Как стать автором
Обновить

Комментарии 38

НЛО прилетело и опубликовало эту надпись здесь
Я думаю, что самый большой вред ARC — это поколение новых программистов, которые сразу стали использовать ARC, и от того плохо понимают модель управления памятью Objective-C в целом.
У меня любимый вопрос на собеседовании — написать мутатор и аксессор для nonatomic NSObject * свойства вручную без использования synthesize и ARC. Пока из всех приходящих людей только один ответил так, чтобы можно было считать его ответ правильным.
НЛО прилетело и опубликовало эту надпись здесь
Именно их и предлагается написать.
А какой ответ вы ждете от испытуемых, и на чем они чаще всего ошибаются?
Я прошу написать примерно следующее:
-(void)setObject:(NSObject *)obj { id tmp = _obj; _obj = [obj retain]; [tmp release]; }

А ошибаются в том, что не могут это написать, хотя это банальщина.

Я даже согласен на двухстрочный грязный вариант без временной переменной, который способен отдать мертвый объект в соседнем потоке, потому что речь не про потокобезопасность, а просто про подсчет ссылок.
Вообще-то, лучше так:
- (void) setObject:(NSObject *)obj
{
  if(obj != _obj) {
    [_obj autorelease];
    _obj = [obj retain];
  }
}
Ваш код даст утечку памяти при работе в потоке без autorelease pool.

Кроме того, проверка во многом бессмысленна.
Ссылки на английскую вики вместо русской мы уже научились давать, это хорошо. Делаем второй шаг — ссылку на официальную документацию вместо ссылки на вики.

Я помогу. Это просто.

developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsautoreleasepool_Class/Reference/Reference.html

Цитирую:
In a reference counted environment, Cocoa expects there to be an autorelease pool always available. If a pool is not available, autoreleased objects do not get released and you leak memory. In this situation, your program will typically log suitable warning messages.


Выделено мной.
Поясню предыдущий вопрос.

Существует достаточно распространенное мнение, что в переопределенных сеттерах для сохранения KVO совместимости необходимо вручную вызывать методы willChangeValueForKey: и didChangeValueForKey:

Однако данное мнение ошибочно, т.к. KVO при подмене класса объекта расширяет сеттеры и вызывает данные методы автоматически. (Если только вручную не отключить автоматическое уведомление, переопределив +automaticallyNotifiesObserversForKey:)

Вот было любопытно, доходит ли разговор до KVO, или все ошибки ещё на этапе retain/release.
Нет, речь не о тонкостях — мы не сеньоров набираем. Речь просто про базовое понимание принципа подсчета ссылок.
Можно не добавлять модификатор __autoreleasing в сигнатуру -(BOOL)save:(NSError * __autoreleasing *) error, т.к. переменная-указатель на id имеет этот модификатор по умолчанию.
Для блока нужно вызывать copy только если он все еще размещен на стеке, т.е. если блок объявлен вне метода это делать особого смысла нет.
NSString *a = (__bridge NSString*)my_cfref; // пустой каст

В этой строчке происходит увеличение количества ссылок, так как переменная имеет модификатор __strong.
Вы правы, надо было пояснить. my_cfref в таком случае останется +1, и ее надо отдельно освобождать.
Блоки по-прежднему необходимо копировать.
//локальная переменная
someBlockType someBlock = ^{NSLog(@"hi");}; 
[someArray addObject:[someBlock copy]];

Мне кажется, интересно разобрать этот момент подробнее.

Во-первых, почему нужно копировать? Потому что по-умолчанию блоки создаются в стеке, а не в куче, проверим:
    int i = 0;
    NSLog(@"%@",[^{NSLog(@"i = %d", i);} class]);
// __NSStackBlock__

Блок класса __NSStackBlock__, т.е по выходу из области видимости указатель на блок станет невалидным, поэтому нам надо скопировать его в кучу, тогда в массиве будет храниться верный указатель:
    int i = 0;
    NSLog(@"%@",[[^{NSLog(@"i = %d", i);} copy] class]);
//__NSMallocBlock__

Очевидно, в результате копирования мы получили блок в куче. Теперь его можно помещать в массив.

Но любопытно, если инициализировать блок через переменную, то копирование не обязательно, блок уже будет в куче.
    int i = 0;
    dispatch_block_t someBlock = ^{NSLog(@"i = %d", i);};
    NSLog(@"%@",[someBlock class]);
// __NSMallocBlock__

Почему так происходит? Потому что по-умолчанию для указателей на объекты (а блок — это объект) применяется модификатор __strong, т.е. код разворачивается в __strong dispatch_block_t someBlock = ...; затем при инициализации strong переменной объекту посылается retain, а в соответствии с документацией ARC такой retain для блоков разворачивается в Block_copy. Итого, блок уже в куче.

Кроме того, примитивные блоки без доступа к окружающему scope никогда не лежат в стеке, а определяются как глобальные константы, операции copy и release на них никак не влияют.
    NSLog(@"%@",[^{NSLog(@"hi");} class]);
    NSLog(@"%@",[[^{NSLog(@"hi");} copy] class]);
// __NSGlobalBlock__
// __NSGlobalBlock__

(Поэтому для предыдущих примеров мне понадобилось ввести переменную «i».)

Но, безусловно, всё равно лучше придерживаться правила всегда копировать блоки при передачи их за область видимости, где они были определены. Просто, понимая внутренние процессы, запомнить это правило по-моему проще.
Вот спасибо, мудрый человек! Всегда знал, что блоки со стека надо копировать, но не мог понять почему и без копирования все работает!
Важная и полезная особенность ARC: сразу после деаллокации слабые и сильные ссылки обнуляются, то есть становятся равными nil.

Сильные в эту фразу очевидно случайно попали, ибо если они не nil, то какая тогда деаллокация?

Ну и технически, если кому интересно, не при деаллокации ссылки обнуляются, а при использовании слабой ссылки вызывается функция objc_loadWeak (или аналог), которая проверяет, жив ли ещё объект, и загружает его. К слову, слабые ссылки из-за этого относительно медленные. Но не настолько, чтобы их опасаться. :) Это определенно очень крутая и на мой взгляд самая полезная штука во всём ARC. Можно отказаться от ручного управления только ради них, если остальное вас не привлекает.

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

На компилятор надейся, да сам не плошай. Компилятор видит далеко не всё, пример вчера приводил. Одна из частых ошибок — использование переменных экземпляра в блоке, в результате чего также ретейнится объект. ( _i эквивалентно self->_i )
Спасибо за замечание относительно сильных ссылок, поправил.
можно допустить ошибку и указатель на объект занулится раньше времени, падения не произойдет, потому что посылка сообщения nil`у валидная операция и тут самая большая проблема. Приложение то будет работать, но может вести себя совсем не так как ожидается и вы будете смотреть и не понимать где же кроется проблема. По мне так лучше пусть оно честно упадет.
Всё верно. Если программная логика подразумеает, что объект существует, а он уже деаллоцировался, то рано или поздно произойдёт что-то нехорошое, тут надо кидать исключение и падать.

Но во-первых, если использовать assign указатель, то программа не обязательно сразу упадёт. Память по адресу указателя может быть всё ещё не перезаписана, или перезаписана объектом, у которого существуют те же методы, что и у предыдущего; в итоге сообщение может быть отправлено успешно, но другому объекту, что ещё хуже, чем недоставка нужного сообщения (представьте к примеру, что хотели удалить что-то не нужное, а в итоге стерли данные пользователя; — уж лучше б ничего не удаляли).

Во-вторых, нет никакой возможности проверить assign указатель на валидность (жив ли ещё объект или нет). А weak элементарно сравниваем с nil (через приведение к strong, но не суть), и по факту сравнения решаем, что делать дальше.

На практике при использовании weak всегда подразумевается возможность зануления.

Ещё, можно включить опцию компилятора, чтобы он предупреждал при отправке сообщений через weak указатели.
Простите за глупый вопрос, вот тут:

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

можно же обойтись без strongSelf, просто используя weakSelf?
Если ничего больше не будете добавлять в блок, — то да.

__typeof(self) __weak weakSelf = self;
SomeBlockType someBlock = ^{
    [weakSelf someMethod];
};
Только спустя все это время понял почему нужно локальную переменную делать))) Иначе self может пропасть в любой момент, даже после прохождения if. Мне стыдно)
А могли бы вы пояснить, что такое можно добавить в блок, что по-другому надо работать внутри с 'weakSelf'? Что-то «ретейнящее»?
К сожалению, не всегда есть возможность использовать ARC. Например, при написании скринсейвера для 10.7 — только GC-compatible, а он не совместим с ARC.
По поводу старичков которые говорят, что нужно писать без ARC, чтобы держать все под контролем, в одной книге написано следующее: Если не доверяют компилятору, пусть откроют код в ассемблированом виде и контролируют по полной)
Вырвано из контекста вероятно или книжка плохая. Когда явно известен факт, что доверять нельзя, это другое.
Используйте ARC. Это проще, безопаснее и сэкономит вам время и нервы.

Ха ха. Сегодня с другом разбирали пол дня его проблему. В итоге ARC создан чтобы не следить за памятью. Но в некоторых случаях он сбоит и нужно то закэшировать переменную, то ещё чего, чтобы он не вставил dealloc. ИМХО найти проблемное место, и то что оно возникает куда большая проблема, чем писать сразу явно, тогда не будет вопросов, или использовать другой язык. Он новичек, а я вообще на object c и swift не пишу, не знали про такие грабли, спасибо за статью, я думал это баг. В следующий раз будем знать куда копать, но это костыли какие то, подкручивать счётчики когда он сам не справляется, а это узнаешь постфактум, зачем он тогда нужен, если полагаться нельзя.
не очень понял этот код
id cachedObject = self.delegate;
if (cachedObject) {
	[self.delegate doSomeTask];
}
cachedObject = nil;

возможно хотелось что-то такое?
id cachedObject = self.delegate;
if (cachedObject) {
	self.delegate = nil;
	[cachedObject doSomeTask];
}

Зачем обнулять делегат? В коде как раз сделано кеширование и проверка на nil, так как cachedObject содержит strong ссылку на делегат он не удалиться до того как cachedObject жива.

Делегат может понадобиться обнулить чтобы гарантировать что ему будет отправлено сообщение лишь однажды при первом срабатывании функции которая имеет подобный код.
А для чего тогда такой огород с cachedObject вообще и почему вызываем doSomeTask не для cachedObject? Разве может объект удалиться если его метод уже вызван? (или self внутри метода не strong??)
Насколько я понимаю такой код:
[self.delegate doSomeTask];

просто не вызовет делегата если он уже nil а если вызовет то внутри метода self будет strong и до конца метода будет эта strong жива а значит и будет жив весь объект.

ну он станет nil только если вручную выставлят проперти в nil, сам обьект не удалиться пока на него есть сильная ссылка(cachedObject).

Не понимаю. Ладно, попробую задать прямой вопрос:
зачем делать так:
id cachedObject = self.delegate;
if (cachedObject) {
	[self.delegate doSomeTask];
}
cachedObject = nil;


когда можно так:
[self.delegate doSomeTask];

?

конкретно этот пример — не знаю. но допусти нужно сделать еще какой-то код только когда есть делегат и вызвать потом метод делегата. если проверять отдельно и потом вызывать — есть шанс что делегата уже не будет.


if(self.delegate){
//SOME LOGIC
 [self.delegate doSomeTask];
}
Ок. Понял эту мысль. Ну и в этом коде стоило бы вызвать doSomeTask для cachedObject ибо может оказаться что
// some logic
была сделана для одного делегата вызвали в итоге другой.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории