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

от автора

Приветствую уважаемых хабражителей!

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!

ссылка на оригинал статьи http://habrahabr.ru/company/mailru/blog/210672/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *