Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

10 малоизвестных вероятностей Objective-C

Anna | 2.07.2014 | нет комментариев

Objective-C — язык с богатым рантаймом, но в данной статье речь пойдёт не о содержимом хедера <objc/runtime.h>, а о некоторых вероятностях самого языка, о которых многие разработчики и не догадываются. Да, на них натыкаешься, читая документацию, подмечаешь про себя «хм, увлекательно, нужно как-нибудь копнуть», но они традиционно стремительно вылетают из головы. А начинающие разработчики Зачастую вообще читают документацию наискосок.

В этой статье я собрал 10 восхитительных на мой взор свойств языка Objective-C. Некоторые свойства самоочевидны, некоторые вдалеке не таковы. За применение некоторых в боевом коде нужно бить по рукам, другие же способны подмогнуть в оптимизации скептических мест кода и в отладке. В конце статьи имеется ссылка на исходник, показывающий на примере все эти фичи.

Выходит, начну с самого «аппетитного» на мой взор: безымянные способы.

1. Безымянные способы


Имя способа задавать не непременно, если у него имеются доводы. Неоднократно встречаются способы типа - (void)setSize:(CGFloat)x :( CGFloat)y, но это дозволено довести и до абсолюта:

@interface TestObject : NSObject

  (id):(int)value;
- (void):(int)a;
- (void):(int)a :( int)b;

@end

// ...

TestObject *obj = [TestObject :2];
[obj :4];
[obj :5 :7];

Комично выгладят и селекторы для таких способов: @selector(:) и @selector(::).

Рекомендации по применению: только в исследовательских целях.

2. Новейший синтаксис применим к любым объектам

Квадратные скобки для доступа к элементам массива либо словаря дозволено применять и со своими объектами. Для этого нужно объявить следующие способы.

Для доступа по индексу:

- (id)objectAtIndexedSubscript:(NSUInteger)index;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index;

Для доступа по ключу:

- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)obj forKeyedSubscript:(id)key;

Используем:

id a = obj[1];
obj[@"key"] = a;

Рекомендации по применению: изредка дозволено, но только если Ваш класс не является коллекцией. Если является, отменнее наследоваться от имеющихся.

3. Неявные @property

Объявление @property в хедере на самом деле объявляет лишь геттер и мутатор для некоторого поля. Если их объявить напрямую, ничего не изменится:

- (int)value;
- (void)setValue:(int)newValue;

obj.value = 2;
int i = obj.value;

Так же неявные свойства — это всякие функции, не принимающие доводов, но возвращающие значение:

NSArray *a = @[@1, @2, @3];
NSInteger c = a.count;

А ещё — функции, имя которых начинается на «set», ничего не возвращающие, но принимающие один довод:

@interface TestObject : NSObject
- (void)setTitle:(NSString *)title;
@end;

//...

TestObject *obj = [TestObject new];
obj.title = @"simple object";

Рекомендации по применению: объявление @property выглядит значительно отменнее, для этого и было введено. К свойствам отменнее обращаться через “.“, а вот обыкновенные способы отменнее вызывать через “[]“. Напротив начинает крепко страдать читаемость кода.

4. Ручное выделение памяти под объект без alloc

Объекты дозволено создавать с поддержкой ветхой добродушной сишной функции calloc, задав потом isaвручную. В тезисе, это лишь замена alloc, а init дозволено отправить и потом.

// Выделяем память, заполненную нулями
void *newObject = calloc(1, class_getInstanceSize([TestObject class]));
// Задаём isa прямой записью в память
Class *c = (Class *)newObject;
c[0] = [TestObject class];
// Тут __bridge_transfer-каст необходим для передачи объекта в ARC - напротив утечёт
obj = (__bridge_transfer TestObject *)newObject;
// Посылаем init - объект готов!
obj = [obj init];

Рекомендации по применению: рекомендуется с превеликой осторожностью. Один из вариантов применения — выделение памяти разом под огромный массив объектов (о чём рассказывал некогда AlexChernyy наCocoaHeads).

5. Распечатать нынешний авторелиз-пул

У ObjC имеются «спрятанные» функции, доступ к которым дозволено получить с поддержкой extern:

extern void _objc_autoreleasePoolPrint(void);

Позже вызова этой функции в консоль будет выведено содержимое нынешнего авторелиз-пула, приблизительно в таком виде:

objc[26573]: ##############
objc[26573]: AUTORELEASE POOLS for thread 0x7fff72fb0310
objc[26573]: 9 releases pending.
objc[26573]: [0x100804000]  ................  PAGE  (hot) (cold)
objc[26573]: [0x100804038]  ################  POOL 0x100804038
objc[26573]: [0x100804040]       0x100204500  TestObject
objc[26573]: [0x100804048]       0x100102fc0  __NSDictionaryM
objc[26573]: [0x100804050]       0x1007000b0  __NSArrayI
objc[26573]: [0x100804058]       0x1006000a0  __NSCFString
objc[26573]: [0x100804060]       0x100600250  NSMethodSignature
objc[26573]: [0x100804068]       0x100600290  NSInvocation
objc[26573]: [0x100804070]       0x100600530  __NSCFString
objc[26573]: [0x100804078]       0x100600650  __NSArrayI
objc[26573]: ##############

Это бывает дюже пригодно для дебага. Дозволено поискать и другие пригодные штуки тут:www.opensource.apple.com/source/objc4/objc4-551.1/

Рекомендации по применению: в дебаге — пожалуйста.

6. Прямой доступ к значениям синтезированных @property

Как теснее говорилось выше, @property лишь генерирует сигнатуры геттера и мутатора. Но если качество синтезированное (через @synthesize либо по умолчанию), то помимо этого генерируется и ivar:

@property NSMutableDictionary *dict;

- (void)resetDict
{
	_dict = nil;
}

Рекомендации по применению: доступ к синтезированному ivar-у напрямую изредка может быть оправдан, но он не вызывает геттер и мутатор, что может привести к неугодным результатам.

7. Доступ к публичным ivar-ам как в конструкциях

Невзирая на очевидность данной фичи, многие разработчики о ней отчего-то не знают. Познание это может быть благотворно для разрешения раздора имён.

@interface TestObject : NSObject
{
@public
	int field;
}

@implementation TestObject

- (void)updateWithField:(int)field
{
	self->field = field;
}

@end

// ...

TestObject *obj = [TestObject new];
obj->field = 200;

Рекомендации по применению: в ObjC отменнее для таких целей применять @property.

8. instancetype

В ObjC имеется восхитительный тип id, тот, что по сути является NSObject *, то есть, самым базовым типом для объектов, к которому дозволено привести всякий иной объект. Комфортно, но в некоторых случаях могут появиться задачи. К примеру:

[[MyClass sharedInstance] count];

Если sharedInstance возвращает id, то код соберётся без предупреждений даже если в MyClass нет способаcount. Если же sharedInstance будет возвращать instancetype, то ворнинг всё же появится, чай компилятор очевидно понимает, что возвращается объект того класса, у которого вызван sharedInstance.

Рекомендации по применению: целесообразно в способах типа init/new/copy и т.п.

9. Проксирование: forwardingTargetForSelector: и forwardInvocation:

Для ООП характерно вступление дополнительных ярусов абстракции при некоторых задачах. К примеру, изредка необходимо из объекта сделать прокси для иного объекта. В этом нам неизменно помогут следующие способы.

Если в нашем объекте не обнаружена имплементация для некоторого селектора, то ему будет послано следующее сообщение:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
	if ([_dict respondsToSelector:aSelector])
	{
		return _dict;
	}
	return [super forwardingTargetForSelector:aSelector];
}

Если объект, тот, что мы проксируем, отвечает на селектор, то пускай он и реагирует на него. Напротив всё таки придётся бросить эксепшн.

Если мы хотим не легко передать селектор иному объекту, но при этом и изменить сам селектор, мы можем воспользоваться дальнейшей парой функций.

Вначале на запрос сигнатуры незнакомого нам способа мы обязаны воротить сигнатуру присутствующего способа проксируемого объекта:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
	NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
	if ([NSStringFromSelector(aSelector) isEqualToString:@"allDamnKeys"])
	{
		sig = [_dict methodSignatureForSelector:@selector(allKeys)];
	}
	return sig;
}

После этого перенаправляем вызов и меняем имя селектора:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
	if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"allDamnKeys"])
	{
		anInvocation.selector = @selector(allKeys);
		[anInvocation invokeWithTarget:_dict];
	}
}

Рекомендации по применению: дюже комфортный механизм для реализации прокси-объектов. Допустимо, кто-то найдёт и другие варианты применения. Основное — не переусердствовать: код должен быть читаем и легко понимаем.

10. NSFastEnumeration

Что ж, в завершение ещё одна увлекательная фича ObjC: циклы for..in. Их поддерживают все дефолтные коллекции, но можем поддержать и мы. Для этого нужно поддержать протокол NSFastEnumeration, а вернее — определить способ countByEnumeratingWithState:objects:count:, но не всё так легко! Вот сигнатура этого способа:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len

Данный способ будет вызван всякий раз, когда runtime захочет получить от нас новую долю объектов. Их мы обязаны записать либо в предоставленный буфер (размер его len), либо выделить свой. Указатель на данный буфер нужно разместить в поле state->itemsPtr, а число объектов в нём воротить из функции. Так же не забываем, что (в документации этого нет) поле state->mutationsPtr не должно быть пустым. Если этого не сделать, то мы получим непредвиденный SEGFAULT. А вот в поле state->state дозволено записать что желательно, но отменнее каждого — записать число теснее отданных элементов. Если отдавать огромнее нечего, необходимо воротить нуль.

Вот мой пример реализации этой функции:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len
{
	if (state->state >= _value)
	{
		return 0;
	}
	NSUInteger itemsToGive = MIN(len, _value - state->state);
	for (NSUInteger i = 0; i < itemsToGive;   i)
	{
		buffer[i] = @(_values[i   state->state]);
	}
	state->itemsPtr = buffer;
	state->mutationsPtr = &state->extra[0];
	state->state  = itemsToGive;
	return itemsToGive;
}

Сейчас дозволено применять:

for (NSNumber *n in obj)
{
	NSLog(@"n = %@", n);
}

Рекомендации по применению: может быть пригодно для облегчения работы с кастомными конструкциями данных, к примеру, с деревьями и связанными списками.

Завершение

Полный начальный код плана доступен на гитхабе. Если у Вас, достопочтенный читатель, есть что дополнить — не стесняйтесь и пишите в комментариях.

Триумфов каждому в разработке и да пребудет с вами Clang Analyzer!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB