Освобождение делегирующего объекта в методе обратного вызова делегата

17

Я пытаюсь выяснить, что рекомендуется для следующей ситуации. Некоторые объекты, такие как CLLocationManager или MKReverseGeocoder, асинхронно отправляют свои результаты методу обратного вызова делегата. Можно ли выпустить экземпляр CLLocationManager или экземпляр MKReverseGeocoder (или какой бы класс он ни был) в методе обратного вызова? Дело в том, что вам больше не нужен этот объект, поэтому вы сообщаете ему прекратить отправку обновлений, установите его делегат на нуль и отпустите объект.

Псевдокод:

@interface SomeClass <CLLocationManagerDelegate>
...
@end

@implementation SomeClass

...

- (void)someMethod
{
    CLLocationManager* locManager = [[CLLocationManager alloc] init];
    locManager.delegate = self;
    [locManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
    // Do something with the location
    // ...

    [manager stopUpdatingLocation];
    manager.delegate = nil;
    [manager release];
}

@end

Мне интересно, будет ли этот шаблон использования считаться всегда ОК, если он считается никогда не ОК или зависит от класса?

Существует очевидный случай, когда освобождение делегирующего объекта будет ошибочным, и если это нужно сделать после того, как он уведомил делегата. Если делегат освобождает объект, его память может быть перезаписана и приложение сработает. (Кажется, что происходит в моем приложении с CLLocationManager в определенном обстоятельстве, как на симуляторе. Я пытаюсь выяснить, является ли это ошибкой симулятора, или если то, что я делаю, в корне ошибочно.)

Я искал, и я не могу найти окончательного ответа на этот вопрос. Кто-нибудь имеет авторитетный источник, который может ответить на этот вопрос?

    
задан Hollance 31.10.2010 в 20:00
источник
  • Если вы видите сбой - другая вещь, которую вы могли бы попробовать, заключалась бы в том, чтобы установить ее авторекламу, а не явно ее освобождать. Хотя, не зная окончательно, это может просто затуманить проблему, а не фиксировать ее на самом деле ... –  Brad 31.10.2010 в 20:33
  • Интересно, я не думал об этом. Интересно, какой пул авторефератов это заберет. –  Hollance 31.10.2010 в 22:07
  • Я не уверен, но каким-то образом это кажется неправильным. –  Peer Stritzinger 31.10.2010 в 22:50

4 ответа

5

Нет. Этот шаблон всегда считается неправильным. Он разбивает Правила управления памятью какао . Объект manager был передан как параметр. Вы не получили его, не добавили, не скопировали, не сохранили. Поэтому вы не должны освобождать его.

    
ответ дан JeremyP 31.10.2010 в 23:51
  • @Hollance: Не в том объеме, которого вы не сделали. Правила понятны. Вы не должны отпускать объект. Он был передан вам в качестве параметра, поэтому вызывающий имеет право ожидать, что вы ничего не сделаете, чтобы он ушел. –  JeremyP 01.11.2010 в 10:18
  • @Hollance: Моя интерпретация правил полностью правильная. Об этом свидетельствует тот факт, что когда вы его попробовали, приложение потерпело крах. Вы не можете освободить этот объект в этом методе, потому что вы не получили его с помощью new, alloc, keep или copy в этом методе. В результате вы удалили объект, пока выполняется метод этого объекта. –  JeremyP 01.11.2010 в 14:41
  • «Это совершенно верно, если остальная часть этого метода не использует переменные экземпляра из этого объекта». Откуда ты это знаешь? У вас есть исходный код для CLLocationManager? И вы ошибаетесь, когда говорите, что у вас есть объект, который был передан как параметр. Ты не. Конец истории. –  JeremyP 01.11.2010 в 15:57
  • Еще один момент для правил управления памятью какао: «Обычно полученный объект остается в силе в том методе, в котором он был принят». Где-то в стеке вызовов что-то получило ваш CLLocationManager, и, выпустив его там, где вы это делаете, вы нарушаете приведенный выше принцип. –  JeremyP 01.11.2010 в 16:06
  • Вы совершенно не поняли смысла. Вы не выделили, новый сохраните или скопируйте объект в этой области. Поэтому вы не владеете им («вы» больше или меньше «текущая область»). И это связано с вопросом, потому что вы спрашиваете, рекомендуется ли вам то, что вы делаете. Не рекомендуется придерживаться правил управления памятью. И я не вижу, как вы можете поддерживать «существенную разницу» перед лицом сбоев в вашем приложении. –  JeremyP 02.11.2010 в 10:32
Показать остальные комментарии
5

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

Это определенно очень плохая идея для выпуска - таким образом освободить объект от его делегата. Просто подумайте о том, как объекты (например, CLLocationManager) звонят своим делегатам - они просто называют их посредине некоторого метода. Когда вызов делегата завершен, выполнение кода возвращается к методу объекта, который уже был освобожден. BAM!

Давайте немного забудем о том, что это плохая идея. Я вижу два варианта, как исправить , что легко. Во-первых, autorelease вместо release дает объекту немного более продолжительного спама - он, по крайней мере, выживет, вернувшись из делегата. Этого должно быть достаточно для большинства случаев, по крайней мере, если автор API хорошо выполнил свою работу и инкапсулировал логику основного класса API (в случае CLLocationManager он может ожидать, что GPS отключится ...). Второй вариант заключается в задержке выпуска ( performSelector:withObject:afterDelay: приходит на ум), но это более обходное решение для плохо реализованных API.

Итак, если выпускать это не очень хорошая идея, то что?

Что вы на самом деле получаете, выпустив CLLocationManager? Освобождение этих нескольких байтов памяти не спасет ваше приложение от завершения, когда система потеряла память. Во всяком случае, действительно ли это только один раз, когда вам нужно местоположение текущего пользователя?

Я предлагаю вам инкапсулировать задачи, связанные с CLLocationManager, в отдельный класс, возможно, даже в singleton - этот класс станет его делегатом, и он позаботится об общении с CLLocationManager и сообщает вашей заявке о результатах (возможно, отправив NSNotification ). CLLocationManager будет освобожден от dealloc этого класса и никогда не будет вызван обратным вызовом делегата. stopUpdatingLocation должно хватить, освобождая несколько байтов памяти - ну, вы можете сделать это, когда ваше приложение входит в фон, но пока ваше приложение работает, освобождение этих нескольких байтов не приводит к существенному улучшению потребления памяти.

** Добавление **

Естественно и правильно, чтобы делегат имел право собственности на объект, для которого он выступает в качестве делегата. Но делегат не должен освобождать объект в результате обратного вызова. Есть одно исключение из этого, хотя, и это обратный вызов, говорящий, что обработка завершена. В качестве примера для этого используется NSURLConnection connectionDidFinishLoading: , которое указано в документации «Делегат не получит никаких дополнительных сообщений». У вас может быть класс, загружающий кучу файлов, каждый из которых имеет другой NSURLConnection (имеющий свой класс в качестве делегата), выделяя и освобождая их, как скорость загрузки файлов.

Поведение

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

    
ответ дан Michal 31.10.2010 в 23:34
  • Все хорошие моменты. Я знаю, как обойти эту проблему, мне просто интересно, что правильно. Одна из причин, почему я задаю этот вопрос, состоит в том, что некоторые полуавторитетные источники (такие как Поваренная книга Эрики Садун) освобождают делегирующий объект от обратных вызовов делегатов в некоторых примерах. Так или иначе, в некоторых случаях (и как знать, в каких случаях это), или эти примеры просто неправильны? –  Hollance 01.11.2010 в 07:13
  • Освобождение всегда плохая идея. Autoreleasing, это зависит - он может работать, но нет никакой гарантии. API-интерфейсы должны быть написаны таким образом, что класс Manager действует как интерфейс между библиотекой и кодом пользователя, поэтому пользователь может ее освободить, а библиотека может самостоятельно управлять до полной очистки (сетевые подключения, аппаратное обеспечение, исправление GPS, что угодно) , Отложите все эти сомнения в сторону и сохраните объект вокруг, особенно если он поддерживает приостановку, как stopUpdatingLocation в случае CLLocationManager. –  Michal 01.11.2010 в 09:25
  • , потому что комментарий ограничен по длине, я добавил больше в нижнюю часть ответа. –  Michal 01.11.2010 в 11:38
  • Ваш ответ близок к тому, что я считаю удовлетворительным, но мне все же хотелось бы увидеть официальную документацию, где объясняется эта идиома программирования. Вы знаете какие-либо такие документы? –  Hollance 02.11.2010 в 18:44
  • Ну, я не читаю книги по программированию (первый и последний был ~ 20 лет назад), и я никогда не видел, чтобы документация по API шла, чтобы объяснить такие деликатесы. Я сомневаюсь, что это где-то написано. Вот почему я благодарен, что у меня есть stackoverflow, ну и друзья, которые тоже разработчики. Обсуждать вещи в нитке вроде этого или над пивом гораздо ценнее, как читать об этом на листе бумаги. –  Michal 02.11.2010 в 21:07
3

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

Однако правильнее было бы просто остановить обновления и установить делегирование менеджеров на нуль, как вы уже делаете. Итак, единственное, что вам нужно удалить, - это строка [manager release].

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

ответ дан twerdster 01.11.2010 в 00:17
1

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

    
ответ дан Kendall Helmstetter Gelner 01.11.2010 в 00:58
  • Верный, но не совсем соответствующий этому вопросу. Я мог бы сохранить locManager в качестве переменной экземпляра. Но тогда я все же хотел бы освободить CLLocationManager, когда я закончил с ним, после чего мне больше не нужна ссылка. –  Hollance 01.11.2010 в 07:16