Подтвердить что ты не робот

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

Я пытаюсь выяснить, что рекомендуется для следующей ситуации. Некоторые объекты, такие как 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 в определенном обстоятельстве, как на симуляторе. Я пытаюсь выяснить, является ли это ошибкой симулятора, или если то, что я делаю, в корне ошибочно.)

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

4b9b3361

Ответ 1

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

Это определенно очень плохая идея для выпуска - таким образом освободить объект от его делегата. Просто подумайте о том, как объекты (например, 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 срок службы будет таким же, как и для его управляющего класса, который также действует как делегат.

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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