...

четверг, 16 января 2014 г.

[Из песочницы] Что нужно знать об ARC

Автоматический подсчет ссылок (Automatic Reference Counting, ARC) для языка Objective-C был представлен компанией Apple еще в 2011 году для iOS 4.0 и выше, Mac OS X 10.6 и выше, с использованием xCode 4.2 и выше. И, хотя всё больше библиотек, фреймворков и проектов сегодня используют ARC, до сих пор среди программистов встречается либо неприятие этой технологии, либо eё неполное понимание. Опытные программисты, привыкшие к retain/release, иногда считают, что они лучше и эффективней справяться с подсчетом ссылок, чем это за них сделает компилятор, а новички, начищающие сразу использовать ARC, полагают, что им не надо вообще думать об управлении памятью, и всё сделается магическим образом само.



ARC — не сборщик мусора




В отличае от сборщика мусора, ARC не занимается автоматическим освобождением памяти от отработанных объектов и не запускает никаких фоновых процессов. Всё что он делает — это при сборке приложения анализирует и расставляет retain/release в компилируемый код за программиста.

Вы не можете напрямую вызвать retain, release, retainCount, autorelease.

Можно переопределить dealloc, но вызывать [super dealloc] не надо, за вас это сделает ARC, а так же освободит при этом все property и instance variables, которые в этом нуждаются. Переопределять dealloc бывает необходимо для того, чтобы, например, отписаться от нотификаций, удалить объект из иных служб и менеджеров, на которые он подписан, инвалидировать таймеры, а так же чтобы освободить не Objective-C объекты.

Код без ARC:


- (void)dealloc
{
[_someIvar release];
[_anotherIvar release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}




Код с ARC:


- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}




Сильные и слабые ссылки




Вместо атрибутов retain и assign для properties в ARC используются атрибуты strong и weak (__strong и __weak для локальных переменных и ivars). Это не совсем их аналоги, но об этом позже. Атрибут strong используется по умолчанию, так что указывать его явно не обязательно.

@property AnyClass *strongReference; //(atomic, strong) по умолчанию
@property (nonatomic, weak) id delegate;




Properties, переменные которых ссылаются на объекты «вверх» по иерархии и делегаты должны быть слабыми, чтобы избежать циклических ссылок.

Поскольку локальные переменные по умолчанию сильные (__strong), возможны такие конструкции в коде:



NSDate *originalDate = self.lastModificationDate;
self.lastModificationDate = [NSDate date];
NSLog(@"Last modification date changed from %@ to %@", originalDate, self.lastModificationDate);




Объект, сохраненный в переменной originalDate будет жив до тех пор, пока ARC не найдет последнюю строку, где используется эта переменная, а затем тут же ее освободит (подставит release).

Для создания слабой ссылки используется атрибут __weak:



NSDate * __weak originalDate = self.lastModificationDate;
self.lastModificationDate = [NSDate date];




В данном примере originalDate может перестать существовать уже на второй строчке, если на этот объект больше нет сильных ссылок.

Важная и полезная особенность ARC: сразу после деаллокации слабые и сильные ссылки обнуляются, то есть становятся равными nil.


А поскольку в Objective-C nil может принимать любые сообщения, проблемы с EXC_BAD_ACCESS уходят в прошлое. Это и есть отличие от атрибутов retain/assign. Подобное происходит и при объявлении объектов: они неявно инициализируются nil'ом. Полезным приёмом является кэширование слабых property в сильных локальных переменных, чтобы быть уверенным, что объект будет жив необходимое время:



- (void)someMethod {
NSObject *cachedObject = self.weakProperty;
[cachedObject doSomething];
[cachedObject doSomethingElse];
}




По этой же причине при проверке слабых property на nil, например, делегатов, нужно их кэшировать:

id cachedObject = self.delegate;
if (cachedObject) {
[self.delegate doSomeTask];
}
cachedObject = nil;




Присваивание кэшированному объекту nil убирает сильную ссылку, и если на него больше нет других сильных ссылок, объект будет деаллоцирован.

В редких случаях нужно, чтобы ссылки на объект после его уничтожения не обнулялись. Для этого существует атрибут unsafe_unretained:



@property (unsafe_unretained) NSObject *someProperty;
NSObject *__unsafe_unretained someReference;


Autorelease




Объекты, созданные с помощью статических методов (например: [NSData data...], [NSArray array...] и т. д.) и литералов (@«string», @42, @[], @{} ) больше не авторелизные. Время жизни таких обьектов задается только сильными ссылками на них.

Существует атрибут __autoreleasing, который, согласно документации, рекомендуется использовать для двойных указателей (* id) в случае, если необходимо передать результат в параметр.

NSError *__autoreleasing error;
BOOL ok = [database save:&error];
if (!ok) {
//обрабатываем ошибку
}




Tогда метод save должен иметь следующую сигнатуру:

- (BOOL)save:(NSError * __autoreleasing *) error;




Это гарантирует сохранность созданного внутри метода объекта. Если объявить переменную без этого атрибута, то компилятор создаст временную авторелизную переменную, которую передаст в метод:

NSError * error;
NSError *__autoreleasing tmp;
BOOL ok = [database save:&tmp];
error = tmp;


NSAutoreleasePool теперь недоступен для прямого использования. Вместо него предлагается использовать директиву @autoreleasepool {}. При входе в такой блок состояние пула сохраняется, при выходе восстанавливается, освобождая все объекты, созданные внутри. Рекомендуется использовать @autoreleasepool в циклах, если создается большое количество временных обьектов.



for (id object in hugeArray) {
@autoreleasepool {
//использование временных объектов
}
}


Блоки




Блоки по-прежднему необходимо копировать.

//property
@property (nonatomic, copy) SomeBlockType someBlock;

//локальная переменная
someBlockType someBlock = ^{NSLog(@"hi");};
[someArray addObject:[someBlock copy]];




О циклических ссылках компилятор предупредит:

warning: capturing 'self' strongly in this block is likely to lead to a retain cycle

[-Warc-retain-cycles,4]



SomeBlockType someBlock = ^{
[self someMethod];
};




Когда блок будет скопирован, он также захватит self, если в нем используются instance varisbles.

Чтобы избежать таких случаев, нужно создать слабую ссылку на self:



__weak SomeObjectClass *weakSelf = self;
SomeBlockType someBlock = ^{
SomeObjectClass *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf someMethod];
}
};




Необходимо заботиться о любых объектах в блоках, объявленных снаружи, используя атрибут __weak.

Строим мосты




Как же быть с объектами CoreFondation? Для них ручной подсчет ссылок никто не отменял. Прямой каст теперь не работает, для этого есть несколько специальных ключевых слов.

id my_id;
CFStringRef my_cfref;
NSString *a = (__bridge NSString*)my_cfref; // пустой каст
CFStringRef b = (__bridge CFStringRef)my_id; // пустой каст
NSString *c = (__bridge_transfer NSString*)my_cfref; // -1 к количеству ссылок CFRef
CFStringRef d = (__bridge_retained CFStringRef)my_id; // вернет CFRef +1



  • __bridge не делает ничего с количеством ссылок, просто приведет объект к нужному типу.

  • __bridge_transfer нужен для смены типа объекта с CF на Objective-C. ARC декрементирует счетчик ссылок CF, так что убедитесь, что он больше нуля.

  • __bridge_retained нужен для смены типа объекта с Objective-C на CF. Он вернет CF-объект c счетчиком ссылок +1. Не забудьте освободить объект вызовом CFRelease().


Заключение




Используйте ARC. Это проще, безопаснее и сэкономит вам время и нервы.
Ссылки



Раздел документации Clang об ARC

Статья о быстродействии ARC

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий