Класс NSNotif icationCenter предоставляет ряд любопытных средств для управления памятью. Вам полезно будет знать о них, поскольку вы, скорее всего, будете пользоваться уведомлениями в своем прикладном коде.

Если вы зарегистрировались в центре уведомлений, используя метод addObserver: sele ctor: паше: object:, значит, вы передали этому центру ссылку на некоторый объект (обычно ссылку self) в качестве первого аргумента данного метода. Это слабая ссылка без поддержки механизма ARC, и поэтому существует опасность, что после того, как данный объект прекратит свое существование, центр уведомлений попытается отправить уведомление по ссылке, которая, по сути, делается на “мусор”. Именно поэтому вам нужно снять объект с регистрации на уведомления, прежде чем произойдет нечто подобное. Аналогичная ситуация возникает и с делегатами, как пояснялось ранее.

Если же вы зарегистрировались в центре уведомлений, используя метод addObserver ForName: obj ect: queue: usingBlock:, то управлять памятью, особенно, если действует механизм ARC, будет намного труднее по следующим причинам.

  • Маркер наблюдателя, возвращаемый в результате вызова метода addObserverForNa me: obj ect: queue: usingBlock:, сохраняется центром уведомлений до его снятия с регистрации.
  • Маркер наблюдателя может также сохранять ваш объект (по ссылке self) через блок. В таком случае до тех пор, пока вы не снимите маркер наблюдателя с регистрации в центре уведомлений, он будет сохраняться в этом центре. Это означает утечку вашего объекта из памяти до тех пор, пока вы не снимите его с регистрации на получение уведомлений. Однако вы не сможете этого сделать в методе dealloc, поскольку его нельзя будет вызвать до тех пор, пока ваш объект зарегистрирован.
  • Кроме того, если ваш объект сохраняет маркер наблюдателя, а тот — ваш объект, то невольно возникает цикл сохранения.

Рассмотрим следующий пример кода, в котором происходит регистрация на получение уведомлений и присваивание маркера наблюдателя переменной экземпляра:

self->_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"heyho"

object:nil queuemil usingBlock: Л (NSNotif ication *n) (

NSLog(@"%@", self);

H;

В данном примере преследуется конечная цель — снять наблюдателя с регистрации. Именно поэтому сохраняется ссылка на него. Это сделать вполне естественно в методе dealloc следующим образом:

- (void) dealloc {

[[NSNotificationCenter defaultCenter] removeObserver:self->_observer];

)

И все же такой прием оказывается неработоспособным. Ведь метод dealloc вообще не вызывается из-за появления цикла сохранения. Объект, доступный по ссылке self, сохраняет наблюдателя, но из-за блока наблюдатель также сохраняет объект self. Следовательно, происходит утечка этого объекта из памяти, и он вообще не регистрируется.

Нарушить цикл сохранения можно двумя способами. Один из них состоит в освобождении объекта _observer после его снятия с регистрации. До тех пор, пока этого не будет сделано, метод dealloc по-прежнему не будет вызываться. Следовательно, нужно найти другое место, кроме метода dealloc, где можно было бы снять с регистрации объект _observer и освободить его.

Так, если речь идет о контроллере типа UlViewController, то одним из таких мест может стать метод viewDidDisappear: из представления этого контроллера. Он вызывается при удалении представления контроллера из пользовательского интерфейса, как показано ниже.

- (void) viewDidDisappear:(BOOL)animated {

[super viewDidDisappear:animated];

[[NSNotificationCenter defaultCenter] removeObserver:self.observer]; self->_observer = nil; II освободить наблюдатель

}

Когда наблюдатель снимается с регистрации, он освобождается центром уведомлений. Когда он освобождается еще и вручную, то уже никем больше не сохраняется. В итоге наблюдатель прекращает свое существование, попутно освобождая объект self, который может затем прекратить свое существование в должном порядке, когда вызывается метод dealloc и утечки этого объекта не происходит. С другой стороны, если переменная экземпляра _observer отмечена как слабая ссылка weak, можно пропустить последнюю строку

в приведенном выше фрагменте кода. Когда наблюдатель снимается с регистрации, он будет освобожден центром уведомлений, а поскольку наблюдатель сохранялся только этим центром, то он разрушается, попутно освобождая объект self.

Такой подход требует аккуратного управления памятью, потому что метод viewDidDisappear: может быть вызван не один раз в течение срока действия контроллера представления. Поэтому придется зарегистрироваться на получение уведомлений в каком-нибудь другом симметричном месте, например, в методе viewWillAppear:. Если объект self не является контроллером представления, то найти подходящее место, кроме метода dealloc, чтобы снять этот объект с регистрации, будет не так-то просто.

На мой взгляд, лучше принять следующее решение: не позволить наблюдателю сохранить объект self. Благодаря этому в первую очередь исключается появление цикла сохранения. Для того чтобы объект self не сохранился в блоке, достаточно не упоминать его (или любую переменную его экземпляра) в блоке. Раз уж не возникает цикл сохранения, то будет вызван метод dealloc, где появится возможность снять с регистрации наблюдатель, что и требовалось изначально сделать.

Так как же избежать упоминания объекта self в блоке, если этому объекту требуется послать сообщение в блоке? Для этого можно воспользоваться несложным, но изящным приемом, называемым “пляской слабых и сильных ссылок” (см. пример 12.9). Эффективность такого приема объясняется тем, что ссылка вообще не упоминается в блоке непосредственно. На самом деле ссылка на объект self не передается в блоке, но в этот момент она становится слабой, чего оказывается достаточно, чтобы предотвратить сохранение в блоке объекта self, а возможно, и возникновение цикла сохранения. В самом же блоке эта ссылка преобразуется в строгую ссылку, а далее все продолжается, как обычно.

 

Пример 12.9. Пляска слабых и строгих ссылок, препятствующая сохранению в блоке объекта self

 weak MyClass* wself = self; О

self->_observer - [[NSNotificationCenter defaultCenter] addObserverForName:@"heyho"

objectrnil queue:nil usingBlock:Л(NSNotification *n) {

MyClass* sself - wself; • if (sself) {

II свободно обратиться к объекту по ссылке sself,

// но ни в коем случае не по ссылке self ©

)

>];

Ниже перечислены стадии “пляски слабых и строгих ссылок”, схематически обозначенные в примере 12.9.

© Локальная слабая ссылка на объект self формируется за пределами блока, но там, где она может быть доступна из блока. Именно такая слабая ссылка и будет передана в блок.

© В блоке эта слабая ссылка присваивается обычной строгой ссылке. Слабые ссылки носят временный характер. Это означает, что объект, доступный по слабой ссылке (даже по ссылке self), может исчезнуть из виду при переходе от одной строки кода к другой. В данном случае слабая ссылка окажется пустой, но отправка сообщения объекту обойдется недешево, а прямое обращение к переменной его экземпляра было бы пагубным. Более того, для проверки, является ли ссылка пустой, отсутствует подходящий потокобезопасный способ. Поэтому в качестве выхода из этого положения слабая ссылка присваивается строгой ссылке.

© Строгая ссылка используется вместо любых ссылок на объект self в блоке. Вся операция, связанная со строгой ссылкой, заключена в проверке на пустую ссылку. Ведь если объект, доступный по слабой ссылке, исчезнет из виду, то пропадет всякий смысл продолжать дальше.

Еще один необычный для управления памятью случай представляет класс NSTimer (см. главу 10). В документации на класс NSTimer говорится, что в циклах исполнения сохраняются их таймеры, а в отношении метода scheduledTimerWithTimelnterval: target: selector: userlnf о: repeats: — целевой объект сохраняется таймером и освобождается, когда таймер становится недействительным. Это означает, что если не сделать периодический таймер недействительным, то целевой объект будет сохранен в цикле исполнения. Единственный способ воспрепятствовать этому — послать таймеру сообщение invalidate. (Подобное затруднение не возникает с непериодическим таймером, поскольку такой таймер становится недействительным сразу же после своего срабатывания.)

При вызове метода scheduledTimerWithTimelnterval:target:selector:userl nfо: repeats: в качестве его аргумента target:, вероятнее всего, предоставляется объект self. Это означает, что объект self сохраняется и не может прекратить свое существование до тех пор, пока таймер не будет сделан недействительным. Однако этого нельзя сделать в собственной реализации метода dealloc, поскольку данный метод невозможно вызвать до тех пор, пока периодически срабатывающему таймеру не будет послано сообщение invalidate. Таким образом, нужно найти какой-нибудь другой подходящий момент для отправки сообщения invalidate таймеру. Другого удобного выхода из этого положения не существует, поэтому остается только подыскать такой момент.

Блоковую альтернативу периодическому таймеру предоставляет технология GCD. “Объект” таймера dispatch_source_t должен быть сохранен, как правило, в качестве переменной

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

@implementation MyClass {

dispatch_source_t _timer; // этим псевдообъектом будет управлять ARC

)

- (void)doStart:(id)sender (

self->_timer = dispatch_source_create(

DISPATCH_SOURCE_TYPE_TIMER,0,0,dispatch_get_main_queue());

dispatch_source_set_timer(

self->_timer, dispatch_walltime(nil, 0),

1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC ) ;

 weak id wself = self;

dispatch_source_set_event_handler(self->_timer, "{

MyClass* sself = wself;      t

if (sself) (

[sself dummy:nil]; // предотвращает цикл сохранения

}

));

dispatch_resume(self->_timer) ;

)

- (void)doStop:(id)sender {

self->_timer = nil;

}

- (void) dummy:   (id)          dummy (

NSLog(@"timer fired");

}

- (void) dealloc (

[self doStop:nil];

)

@end

Другие классы Cocoa с необычным управлением памятью, как правило, ясно разъясняются в документации. Например, в документации на класс UlWebView предупреждается, что перед освобождением экземпляра класса UlWebView, для которого установлен делегат, нужно сначала установить пустое значение (nil) его свойства delegate. Объект класса CAAnimation сохраняет свой делегат. Это исключительный случай, способный доставить немало хлопот, если не знать о нем.

Имеются также случаи, когда в документации отсутствуют особые соображения по поводу управления памятью, но сам механизм ARC может предупредить о возможном возникновении цикла сохранения из-за применения самоссылки в блоке. Примером тому служит обработчик завершения кода в методе экземпляра setViewControllers : direction: animat ed: completion: класса UlPageViewController. Если в блоке completion : содержится ссылка на тот же самый экземпляр класса UlPageViewController, которому этот метод посылается, то компилятор выдаст предупреждение о том, что строгая фиксация контроллера

представления страницы (pvc или под другим именем), скорее всего, приведет к возникновению цикла сохранения. Вместо этого можно организовать слабую фиксацию контроллера представления страницы pvc, применяя описанный ранее прием “пляски слабых и строгих

 

В отношении управления памятью основные классы коллекций, внедренные в версии iOS 6, в том числе NSPointerArray, NSHashTable и NSMapTable, аналогичны соответствующим классам NSMutableArray, NSMutableSet и NSMutableDictionary, но выбор конкретной стратегии управления памятью, выделяемой для объектов этих классов, зависит от вас. Например, объект типа NSHashTable, создаваемый методом класса weakObjectsHashTable, поддерживает слабые ссылки на свои элементы. При действующем механизме ARC эти слабые ссылки заменяются пустыми, если подсчет сохраняемых ссылок на указываемый ими объект уменьшается до нуля. Можно найти немало примеров применения этих классов для исключения циклов сохранения.


 

 

 

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