Почему объект не освобождается при использовании ARC + NSZombieEnabled

Я преобразовал свое приложение в ARC и заметил, что объект, размещенный в одном из моих контроллеров представления, не освобождается, когда этот контроллер представления освобождается. Потребовалось время, чтобы понять, почему. Я включил «Включить объекты зомби» для своего проекта во время отладки, и это оказалось причиной. Рассмотрим следующую логику приложения:

1) Пользователи вызывают действие в RootViewController, которое приводит к созданию SecondaryViewController и представлению через presentModalViewController:animated.

2) SecondaryViewController содержит ActionsController, который является подклассом NSObject.

3) ActionsController наблюдает за уведомлением через NSNotificationCenter, когда оно инициализируется, и прекращает наблюдение, когда оно освобождается.

4) Пользователь закрывает SecondaryViewController, чтобы вернуться к RootViewController.

При выключенном параметре «Включить объекты-зомби» описанное выше работает нормально, все объекты освобождаются. При включении объектов-зомби на ActionsController не освобождается, даже если SecondaryViewController освобождается.

Это вызвало проблемы в моем приложении, потому что NSNotificationCenter продолжает отправлять уведомления ActionsController, и результирующие обработчики вызывают сбой приложения.

Я создал простое приложение, иллюстрирующее это, по адресу https://github.com/xjones/XJARCTestApp. Посмотрите в журнале консоли включение/выключение объектов-зомби, чтобы убедиться в этом.

ВОПРОС(Ы)

  1. Это правильное поведение Enable Zombie Objects?
  2. Как мне реализовать этот тип логики, чтобы устранить проблему. Я хотел бы продолжить использовать Включить объекты-зомби.

РЕДАКТИРОВАТЬ № 1: по предложению Кевина я отправил это в Apple и openradar по адресу http://openradar.appspot.com/10537635< /а>.

РЕДАКТИРОВАНИЕ № 2: пояснение к хорошему ответу

Во-первых, я опытный iOS-разработчик и прекрасно разбираюсь в ARC, зомби-объектах и ​​т. д. Если я что-то упускаю, конечно, я ценю любое освещение.

Во-вторых, это правда, что обходной путь для этого конкретного сбоя заключается в удалении actionsController в качестве наблюдателя, когда secondaryViewController освобождается. Я также обнаружил, что если я явно установлю actionsController = nil, когда secondaryViewController будет освобожден, он будет освобожден. Оба они не являются хорошим обходным путем, потому что они фактически требуют, чтобы вы использовали ARC, но кодировали так, как будто вы не используете ARC (например, nil iVars явно в Dealloc). Конкретное решение также не помогает определить, когда это может быть проблемой в других контроллерах, поэтому разработчики детерминировано знают, когда и как обойти эту проблему.

Хороший ответ объяснил бы, как детерминистически узнать, что вам нужно сделать что-то особенное по отношению к объекту при использовании ARC + NSZombieEnabled, чтобы он решал этот конкретный пример, а также применялся в целом к ​​проекту в целом, не оставляя возможности для других подобных проблемы.

Вполне возможно, что хорошего ответа не существует, так как это может быть ошибка в XCode.

Спасибо всем!


person XJones    schedule 06.12.2011    source источник
comment
Это похоже на ошибку. Пожалуйста, отправьте отчет об ошибке и прикрепите пример проекта.   -  person Lily Ballard    schedule 07.12.2011
comment
Спасибо @Kevin, я отправил отчет об ошибке в Apple.   -  person XJones    schedule 07.12.2011
comment
В отдельной заметке вам, вероятно, следует добавить .DS_Store к вашему глобальному игнорированию git.   -  person Jim    schedule 07.12.2011
comment
ой, мой .gitconfig был испорчен. спасибо, что заметили @jim.   -  person XJones    schedule 07.12.2011


Ответы (5)


Оказывается, я написал какую-то серьезную чушь

Если бы зомби работали так, как я изначально написал, включение зомби напрямую привело бы к бесчисленным ложным срабатываниям...

Происходит некоторое взбалтывание isa, вероятно, в _objc_rootRelease, поэтому любое переопределение dealloc все равно должно вызываться с включенными зомби. Единственное, что не произойдет с зомби, — это фактический вызов object_dispose — по крайней мере, не по умолчанию.

Что забавно, так это то, что если вы немного зарегистрируете, вы увидите, что даже с включенным ARC ваша реализация dealloc будет вызывать реализацию своего суперкласса.

На самом деле я предполагал, что вообще этого не увижу: поскольку ARC генерирует эти забавные .cxx_destruct методы для удаления любых __strong переменных класса, я ожидал увидеть этот вызов метода dealloc — если он реализован.

По-видимому, установка NSZombieEnabled на YES приводит к тому, что .cxx_destruct вообще не вызывается — по крайней мере, так произошло, когда я редактировал ваш образец проекта:
выключение зомби приводит к обратной трассировке и обеим деалокам, а включение зомби не приводит к обратной трассировке и только к одной расторгнуть

Если вам интересно, дополнительное ведение журнала содержится в форке примера проекта — работает, просто запустив: есть два общих схемы для зомби вкл/выкл.


Оригинальный (бессмысленный) ответ:

Это не баг, а фича.

И это не имеет никакого отношения к АРК.

NSZombieEnabled в основном заменяет dealloc для реализации, которая, в свою очередь, isa-заменяет тип этого объекта на _NSZombie — фиктивный класс, который взрывается, как только вы отправляете ему какое-либо сообщение. Это ожидаемое поведение и, если я не ошибаюсь, задокументировано.

person danyowdee    schedule 26.12.2011
comment
Реализация была бы лучше, если бы была переключена только функция Dealloc NSObject, в то время как Dealloc для всех подклассов по-прежнему вызывался бы. Таким образом, код Dealloc будет работать (и ActionsController перестанет прослушивать уведомления), но объект по-прежнему будет _NSZombie для отлова ошибок. - person Tal Bereznitskey; 28.12.2011
comment
Я не думаю, что это правильно. С NSZombieEnabled исходный dealloc все еще должен вызываться. Это происходит в версии проекта, отличной от ARC, и это происходит для некоторых объектов в версии ARC, но не для всех. Таким образом, побудив мою проблему (и вопрос). - person XJones; 29.12.2011
comment
@XJones Да, я серьезно задумался и исправил ... Извиняюсь! - person danyowdee; 29.12.2011
comment
Никаких проблем, @danyowdee. Я ценю время, которое вы потратили на это. Apple не ответила на мой отчет об ошибке, поэтому я все еще в неведении. Я открыт для того, чтобы это было недоразумением с моей стороны (обычно такие вещи), но я не придумал никакого объяснения, почему это желаемое или предполагаемое поведение. - person XJones; 29.12.2011
comment
На данный момент это выглядит для меня как ошибка времени выполнения: обычно object_dispose вызывает .cxx_destruct (вы можете легко проверить это с помощью моего форка вашего проекта). Эта функция, в свою очередь, вызывается -[NSObject dealloc], который — с включенными зомби — заменяется, чтобы заменить isa объекта соответствующим классом _NSZombie. Поэтому я бы сказал, что это оплошность: среда выполнения должна вызывать objc_destructInstance или object_cxxDestructFromClass вместо -[NSObject dealloc], но это не так. - person danyowdee; 29.12.2011
comment
PS: Это отличный вопрос, и я многому научился, пытаясь ответить на него ;-) Оооо, и я обманываю вашу ошибку! - person danyowdee; 29.12.2011
comment
Я только что наткнулся на это, и у меня есть еще один вопрос: 9002191#9002191" title="странная проблема с дугой, не выпускающая ivar в подклассе uiview">stackoverflow.com/questions/8989218/ . Кстати, я проголосовал за то, чтобы закрыть его как обман. Но в моем ответе на мой собственный вопрос вы увидите гораздо более урезанное приложение, которое показывает ошибку. Я тоже зарегистрировался как радар. Надеюсь скоро исправят! Я потерял полдня на этом! - person mattjgalloway; 25.01.2012

Apple признала эту ошибку в Технических вопросах и ответах QA1758.

Вы можете обойти это в iOS 5 и OS X 10.7, скомпилировав этот код в свое приложение:

#import <objc/runtime.h>

@implementation NSObject (ARCZombie)

+ (void) load
{
    const char *NSZombieEnabled = getenv("NSZombieEnabled");
    if (NSZombieEnabled && tolower(NSZombieEnabled[0]) == 'y')
    {
        Method dealloc = class_getInstanceMethod(self, @selector(dealloc));
        Method arczombie_dealloc = class_getInstanceMethod(self, @selector(arczombie_dealloc));
        method_exchangeImplementations(dealloc, arczombie_dealloc);
    }
}

- (void) arczombie_dealloc
{
    Class aliveClass = object_getClass(self);
    [self arczombie_dealloc];
    Class zombieClass = object_getClass(self);

    object_setClass(self, aliveClass);
    objc_destructInstance(self);
    object_setClass(self, zombieClass);
}

@end

Дополнительную информацию об этом обходном пути вы найдете в моем блоге Отладка с помощью ARC и Зомби включены.

person 0xced    schedule 18.08.2012
comment
Спасибо, что добавили это. Очень полезно. - person XJones; 29.08.2012
comment
Не могли бы вы включить в этот ответ обновление, касающееся ответа @gparker на вашу запись в блоге? Я чувствую, что это важно, но не хочу редактировать свой ответ, так как это изменит его содержание, а не форму. - person danyowdee; 31.08.2012

Оказывается, это баг iOS. Apple связалась со мной и сообщила, что они исправили это в iOS 6.

person XJones    schedule 13.07.2012

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

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

person carbonbasednerd    schedule 07.12.2011
comment
Удаление actionsController в качестве наблюдателя, когда secondaryViewController освобождается, действительно решает сбой (я уже сделал это), но не решает проблему того, что объект не освобождается. См. мой пример проекта, в котором нет никаких уведомлений. Я также могу явно установить actionsController в nil в этом случае, но это тоже не очень хорошее решение. Это исправление для этой конкретной проблемы, но оно не дает уверенности в отношении объектов в остальной части приложения. Я уточню тип решения, требуемого в вопросе. - person XJones; 07.12.2011

Поскольку у вас открыт NSZombieEnabled, это позволяет объекту не вызывать Dealloc и помещать объект в специальное место. вы можете закрыть NSZombieEnabled и повторить попытку. И дважды проверьте, есть ли в вашем коде условие сохранения круга.

person user501836    schedule 18.05.2012
comment
это неправильно. метод Dealloc вызывается для объектов-зомби. см. ответ и комментарии @danyowdee. - person XJones; 31.05.2012