Objective-C с ARC: пользовательский сеттер не сохраняет

ПРИМЕЧАНИЕ. Это было связано с ошибкой в ​​некоторых бета-версиях XCode, которые уже давно исправлены. Этот вопрос и ответ, вероятно, не помогут вам, если у вас возникнут проблемы с ARC.


Я переношу свой проект с ручного подсчета ссылок на ARC и наткнулся на проблему: Как я могу гарантировать, что пользовательский установщик для свойства сохранения действительно сохраняет?

В myClass.h я объявил свойство: @property (retain) NSDate *date. Неважно, вручную ли я устанавливал __strong ivar или автогенерировал.

В реализации я, конечно, @synthesize date, и внедрил настраиваемый наборщик (или просто скачать демонстрационный проект Xcode):

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    date = newDate;
  }
}

Это, похоже, не сохраняет дату и дает мне message sent to deallocated instance, когда newName (автоматически) освобождается там, откуда он появился, при попытке получить доступ к myClass.date позже (при условии, что Zombie включен, в противном случае он просто падает тихо).

Изменение настройки, используемой date = [newDate copy], работает вокруг ошибки, но на самом деле я этого не хочу. Удаление пользовательского сеттера также работает, но явно нежелательно.

Что мне здесь не хватает? Как я могу гарантировать, что пользовательский сеттер для сохраняемого свойства фактически сохраняется в среде ARC? Это кажется такой основной и общей задачей, что я думаю, что я пропускаю что-то очень очевидное.

(ПРИМЕЧАНИЕ. Это не подпадает под условия любой Apple NDA, поскольку ARC публично выпущена как часть LLVM)

EDIT: Я создал небольшой проект Xcode для демонстрации проблемы и загрузки ее в github. Не стесняйтесь загрузить его и поиграть. Я нахожусь в своем уме (хотя мой остроумие сегодня не в лучшем состоянии, по общему признанию).

EDIT: для этого типового проекта эта проблема решена (см. принятый ответ). К сожалению, в более крупном проекте, на который я не могу поделиться, проблема сохраняется. В качестве обходного пути я добавил дублирующие свойства strong с синтезированными сеттерами (ivars не работает). Новый пользовательский сеттер теперь выглядит следующим образом:

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    self.date_arcretain = newDate; //this property is only there as a workaround. ARC properly retains it, but only if the setter is synthesized
    date = newDate;
  }
}

Ответ 1

Это выглядит как ошибка для меня; ваш код должен быть в порядке. Если вы еще этого не сделали, укажите ошибку в http://bugreport.apple.com и приложите свой примерный проект.

Изменить. При дальнейшем изучении вашего образца проекта это не ошибка.

Переопределенный объект в вашем примере проекта не является экземпляром NSDate. Вы можете полностью прокомментировать вызов tc.date = now в своем примере проекта, и вы все равно увидите тот же крах. Фактически, вы можете полностью удалить материал NSDate. Перевыпущенный объект фактически является объектом TestVC.

Вот что происходит.

В iOS 4.0, UIWindow получил свойство rootViewController. Если раньше вы просто вызывали [self.window addSubview:myRootcontroller.view] при запуске приложения, это изменение теперь означало, что в окне действительно будет ссылка на контроллер корневого представления. Это важно для передачи уведомлений о ротации и т.д. В прошлом я полагаю, что UIWindow автоматически попытается установить rootViewController, когда было добавлено первое подвью (если оно еще не было установлено), но в вашем примере проекта, который явно не происходит. Это может быть связано с тем, как вы создаете представление, или может быть связано с изменением в iOS 5.0. В любом случае, это не было документированное поведение, поэтому вы не можете полагаться на это.

В большинстве случаев у вашего делегата приложения будет ivar, указывающий на контроллер корневого представления. Это не требуется строго, но обычно это происходит. Однако в представленном вами образце проекта диспетчер представлений не принадлежит делегату приложения. Вы также не установили его в качестве контроллера представления корневого окна. В результате в конце метода -application:didFinishLaunchingWithOptions: ничего не осталось с сильной ссылкой на контроллер вида. Делегат вашего приложения не держится за него, и само окно не держится за него. Таким образом, ARC рассматривает его как локальную переменную (которая есть) и освобождает ее в конце метода.

Конечно, это не изменяет тот факт, что ваш UIButton все еще имеет метод действия, нацеленный на ваш контроллер. Но, как указано в документации, -addTarget:action:forControlEvents: не сохраняет цель. Таким образом, UIButton имеет свисающую ссылку на ваш контроллер просмотра, который теперь освобожден, так как никто не имеет к нему ссылки. Отсюда сбой.

Исправление для этого - изменить эту строку в делетете приложения:

[self.window addSubview:tc.view];

:

self.window.rootViewController = tc;

С этим единственным изменением все теперь прекрасно работает.

Изменить. Также убедитесь, что параметр "Precheck code for ARC migration" не включен, так как это приведет к тому, что компилятор обработает код вручную, и это не приведет к правильному удержанию/release.

Ответ 2

Я установил его на своем Mac с вашим проектом. Ошибка в вашем AppDelegate, а не в свойстве даты. Сделать контроллер вида сохраняемым свойством в AppDelegate. В настоящее время ваш контроллер просмотра автореализован, включая все его свойства.