Приветствую уважаемых хабражителей!
Objective-C — язык с богатым рантаймом, но в данной статье речь пойдёт не о содержимом хедера
В этой статье я собрал 10 удивительных на мой взгляд свойств языка Objective-C. Некоторые свойства самоочевидны, некоторые далеко не таковы. За использование некоторых в боевом коде надо бить по рукам, другие же способны помочь в оптимизации критических мест кода и в отладке. В конце статьи имеется ссылка на исходник, показывающий на примере все эти фичи.
Итак, начну с самого «вкусного» на мой взгляд: безымянные методы.
Имя метода задавать не обязательно, если у него имеются аргументы. Нередко встречаются методы типа
Забавно выгладят и селекторы для таких методов:
Рекомендации по использованию: только в исследовательских целях.
Квадратные скобки для доступа к элементам массива или словаря можно использовать и со своими объектами. Для этого надо объявить следующие методы.
Для доступа по индексу:
Для доступа по ключу:
Используем:
Рекомендации по использованию: иногда можно, но только если Ваш класс не является коллекцией. Если является, лучше наследоваться от имеющихся.
3. Неявные
Объявление
Так же неявные свойства — это любые функции, не принимающие аргументов, но возвращающие значение:
А ещё — функции, имя которых начинается на «set», ничего не возвращающие, но принимающие один аргумент:
Рекомендации по использованию: объявление
4. Ручное выделение памяти под объект без
Объекты можно создавать с помощью старой доброй сишной функции
Рекомендации по использованию: рекомендуется с превеликой осторожностью. Один из вариантов использования — выделение памяти разом под большой массив объектов (о чём рассказывал некогда AlexChernyy на CocoaHeads).
UPD: Благодаря пользователю Trahman было установлено, что данный подход не работает на iOS x64. С его же помощью было найдено портабельное решение:
У ObjC имеются «скрытые» функции, доступ к которым можно получить с помощью
После вызова этой функции в консоль будет выведено содержимое текущего авторелиз-пула, примерно в таком виде:
Это бывает очень полезно для дебага. Можно поискать и другие полезные штуки здесь: www.opensource.apple.com/source/objc4/objc4-551.1
Рекомендации по использованию: в дебаге — пожалуйста.
6. Прямой доступ к значениям синтезированных
Как уже говорилось выше,
Рекомендации по использованию: доступ к синтезированному ivar-у напрямую иногда может быть оправдан, но он не вызывает геттер и мутатор, что может привести к нежелательным эффектам.
Несмотря на очевидность данной фичи, многие разработчики о ней почему-то не знают. Знание это может быть полезно для разрешения конфликта имён.
Рекомендации по использованию: в ObjC лучше для таких целей использовать @property.
8.
В ObjC имеется замечательный тип
Если
Рекомендации по использованию: уместно в методах типа
9. Проксирование:
Для ООП свойственно введение дополнительных уровней абстракции при некоторых проблемах. К примеру, иногда нужно из объекта сделать прокси для другого объекта. В этом нам всегда помогут следующие методы.
Если в нашем объекте не найдена имплементация для некоторого селектора, то ему будет послано следующее сообщение:
Если объект, который мы проксируем, отвечает на селектор, то пусть он и реагирует на него. Иначе всё таки придётся кинуть эксепшн.
Если мы хотим не просто передать селектор другому объекту, но при этом и изменить сам селектор, мы можем воспользоваться следующей парой функций.
Сначала на запрос сигнатуры неизвестного нам метода мы должны вернуть сигнатуру существующего метода проксируемого объекта:
Затем перенаправляем вызов и меняем имя селектора:
Рекомендации по использованию: очень удобный механизм для реализации прокси-объектов. Возможно, кто-то найдёт и другие варианты использования. Главное — не переусердствовать: код должен быть читаем и легко понимаем.
10.
Что ж, в заключение ещё одна интересная фича ObjC: циклы
Этот метод будет вызван каждый раз, когда runtime захочет получить от нас новую порцию объектов. Их мы должны записать либо в предоставленный буфер (размер его
Вот мой пример реализации этой функции:
Теперь можно использовать:
Рекомендации по использованию: может быть полезно для упрощения работы с кастомными структурами данных, к примеру, с деревьями и связанными списками.
Полный исходный код проекта доступен на гитхабе. Если у Вас, уважаемый читатель, есть что дополнить — не стесняйтесь и пишите в комментариях.
Успехов всем в разработке и да пребудет с вами Clang Analyzer!
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).
UPD: Благодаря пользователю Trahman было установлено, что данный подход не работает на iOS x64. С его же помощью было найдено портабельное решение:
object_setClass((__bridge id)newObject, [TestObject class]);
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!