Механизм ARC действует автоматически и бездумно. Ему ничего не известно о логике отношений между объектами в приложении.

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

Цикл сохранения — это ситуация, в которой каждый из объектов А и В сохраняют друг друга. Если в подобной ситуации объектам разрешено быть сохраненными, это в конечном итоге приведет к утечке обоих объектов из памяти, поскольку подсчет сохраняемых ссылок ни на один из них нельзя уменьшить до нуля. Эту ситуацию можно рассматривать и следующим образом: сохраняя объект В, объект А сохраняет также самого себя, а это препятствует его уничтожению.

Цикл сохранения может возникнуть совершенно невинно, поскольку отношения в графе объекта могут оказаться двухсторонними. Например, в системе заказов и товаров должны быть известны как заказываемые товары, так и заказы, в которых перечисляются товары. Поэтому может возникнуть ситуация, когда заказ сохраняет свои товары, а отдельный товар сохраняет свои заказы. В таком случае образуется цикл сохранения. В качестве примера, иллюстрирующего подобное затруднение, рассмотрим простой класс MyClass с единственной переменной экземпляра _thing и открытым методом установки setThing:, а также регистрацией в методе dealloc, как показано ниже.

@implementation MyClass { id _thing;

}

- (void) setThing: (id) what {

 

- (void)dealloc (

NSLog(@"%0", @"dealloc");

}

@end

Теперь выполните следующий фрагмент кода:

MyClass* ml = [MyClass new];

MyClass* m2 = [MyClass new];

ml.thing = m2;

m2.thing = ml;

Теперь объекты, на которые указывают переменные ml и m2, сохраняют друг друга, поскольку механизм ARC по умолчанию сохраняет их во время присваивания. При выполнении приведенного выше фрагмента кода метод dealloc вообще не вызывается ни для одного из экземпляров класса MyClass — даже после того, как переменные ml и m2, автоматически указывающие на их объекты, выйдут из области своего действия и будут уничтожены. В конечном итоге произойдет утечка памяти самих объектов типа MyClass.

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

(^implementation MyClass {

 weak id _thing;

}

Теперь цикл сохранения не возникнет. В рассматриваемом здесь примере утечка памяти не возникает, а оба объекта типа MyClass прекращают свое существование по завершении выполнения кода из данного примера. Механизм ARC отправит каждому из них сообщение release, чтобы уравновесить новые вызовы для их создания, как только переменные ml и m2, автоматически указывающие на эти объекты, выйдут из области своего действия, и никто больше не попытается их сохранить.

 

В механизме ARC ссылка, не объявленная явно слабой, считается строгой ссылкой. Следовательно, строгой оказывается такая ссылка, по которой механизм ARC сохраняет объект во время присваивания. На самом деле для обозначения строгих ссылок имеется специальный описатель strong, но на практике вам вообще не придется его применять, поскольку это делается по умолчанию. Имеются еще два дополнительных, но редко применяемых описателя:  unsafe_unretained и autoreleasing.

 

На практике слабая ссылка чаще всего применяется для связывания объекта с его делегатом (см. главу 11). Делегат является независимой сущностью, и поэтому у объекта нет никаких причин завладеть своим делегатом. На самом деле объект, как правило, обслуживает делегат, а не владеет им. Владение как таковое зачастую следует другим путем: объект А может создать и сохранить объект В, а также сделаться делегатом объекта В. Это может привести к возникновению цикла сохранения. Следовательно, большинство делегатов должны быть объявлены как слабые ссылки. Например, в исходном коде проекта, созданном в среде Xcode по шаблону Utility Application, можно обнаружить следующую строку:

 

@property (weak, nonatomic) id <FlipsideViewControllerDelegate> delegate;

где ключевое слово weak служит для объявления свойства, как подробнее поясняется далее в этой главе. Это равнозначно объявлению переменной экземпляра ^delegate как слабой ссылки weak.

Для того чтобы воспрепятствовать в коде без поддержки механизма ARC возникновению цикла сохранения по ссылке, достаточно не сохранять объект, когда он, прежде всего, присваивается этой ссылке. Ведь управление памятью вообще не распространяется на ссылку. Это можно видеть на примере слабой ссылки, хотя она и не подобна слабой ссылке при поддержке механизма ARC. Слабая ссылка без поддержки механизма ARC рискует превратиться в висячий указатель, когда экземпляр, на который она указывает, освобождается (каким-нибудь другим, сохранившим его объектом) и затем уничтожается. Следовательно, ссылка может быть непустой, но указывать на “мусор”, и поэтому посылка ей сообщения может иметь таинственно пагубные последствия.

Как ни странно, этого не может произойти со слабой ссылкой при действующем механизме ARC. Когда подсчет сохраняемых ссылок на экземпляр достигает нуля, а сам экземпляр близок к исчезновению, при действующем механизме ARC любая слабая ссылка, указывавшая на него, автоматически становится пустой! (Это поразительное действие происходит подспудно в ходе служебных операций, когда объект присваивается слабой ссылке. По существу, механизм ARC отмечает этот факт в блокнотном списке.) Это еще одна причина, по которой следует, по возможности, отдавать предпочтение механизму ARC. Ведь этот механизм зачастую пренебрежительно, хотя и аккуратно относится к слабым ссылкам в коде, где он не поддерживается, считая их “небезопасными”. (Такие ссылки на самом деле относятся к типу  unsafe_unretained, как упоминалось ранее.)

К сожалению, в большей части среды Cocoa механизм ARC не применяется. Управление памятью в среде Cocoa тщательно выписано, и поэтому операции сохранения и освобождения объектов из памяти в общем уравновешены, чтобы не вызывать утечек памяти. Тем не менее свойства встроенных классов Cocoa, поддерживающие слабые ссылки, являются слабыми ссылками без поддержки механизма ARC, поскольку они стары и обратно совместимы, тогда как механизм ARC является новым. Такие свойства объявляются с помощью ключевого слова assign. Например, свойство delegate из класса UINavigationController объявляется следующим образом:

@property(nonatomic, assign) id<UlNavigationControllerDelegate> delegate

Следовательно, даже если в прикладном коде применяется механизм ARC, отсутствие его поддержки в среде Cocoa означает, что ошибки управления памятью по-прежнему возможны. Так, ссылка на делегат класса UINavigationController может в конечном итоге превратиться в висячий указатель на “мусор”, если объект, на который она делается, прекратит свое существование. Если же кто-нибудь (вы или среда Cocoa) попытается послать сообщение по такой ссылке, приложение завершится аварийным сбоем, а поскольку нечто подобное, как правило, происходит намного позже момента совершения настоящей ошибки, то выяснить истинную причину аварийного сбоя будет очень трудно. Типичные признаки такого сбоя состоят в том, что он происходит в методе objc retain от компании Apple и упоминается в исключении EXC_BAD_ACCESS (рис. 12.1). В таком случае, возможно, придется активизировать объекты-зомби в целях отладки прикладного кода, как пояснялось ранее в этой главе.

Предотвращение подобной ситуации входит в ваши обязанности. Если вы присвоите ка-кой-нибудь объект (например, делегат класса UINavigationController) слабой ссылке без поддержки механизма ARC и если данный объект собирается прекратить свое существование в тот момент, когда эта ссылка по-прежнему существует, то вы обязаны присвоить ей пустое значение (nil) или какой-нибудь другой объект, чтобы сделать ее безвредной.

 

Аварийный сбой в результате отправки сообщения по висячему указателю

Pиc. 12.1. Аварийный сбой в результате отправки сообщения по висячему указателю

 


 

 

 

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