MKMapView по-прежнему отправляет сообщения делегату после того, как супервизор был отключен

EDIT: изменено название. Я не знал этого в то время, но это дубликат Почему я сбой после освобождения MKMapView, если я больше не использую его?


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

Сценарий:

  • RootViewController имеет tableView, отображающий кучу элементов
  • Выбор ячейки представляет модальный detailViewController, содержащий другой tableView
  • Одна из ячеек таблицы в detailViewController содержит MKMapView, показывающую расположение элемента
  • mapView.delegate = detailViewController
  • Отключить модальный detailViewController

Вскоре после этого приложение сбой b/c MKMapView отправляет mapView:viewForAnnotation: в dealloc'ed detailViewController. Этот сбой, воспроизведенный на пользовательском устройстве с раскладкой ad-hoc, поэтому проблема не имеет ничего общего с NSZombieEnabled.

Мне удалось решить проблему, добавив:

_mapView.delegate = nil;

к dealloc методу tableViewCell, содержащему mapView.

ВОПРОС: почему необходимо, чтобы делегат был отключен, когда ячейка отменена? Похоже, что mapView должен быть отменен ARC, когда ячейка освобождена, оставив это ненужным. Это хорошая практика для ноль делегатов, но я не думал, что это потребуется в этом случае.

EDIT: все подпункты как detailViewController, так и UITableViewCells объявлены как (nonatomic, strong) свойства ala:

@property (nonatomic, strong)   MKMapView *         mapView;

РЕДАКТИРОВАТЬ 2: Думаю, мне нужно лучше читать документы. @fluchtpunkt верен. Здесь соответствующая информация из документации MKMapView:

Перед выпуском объекта MKMapView, для которого вы установили делегат, не забудьте установить для свойства делегирования объектов значение nil. Один место, которое вы можете сделать, это метод dealloc, в котором вы распоряжаетесь вид карты.

Ответы

Ответ 1

MKMapView не скомпилирован с ARC, и из-за этого свойство для delegate по-прежнему объявляется как assign вместо weak.
Из Документация MKMapView:

@property(nonatomic, assign) id<MKMapViewDelegate> delegate

И из Переход к заметкам о выпуске ARC:

Вы можете реализовать метод dealloc, если вам нужно управлять ресурсами, отличными от освобождения переменных экземпляра. Вам не нужно (действительно, вы не можете) выпускать переменные экземпляра, но вам может потребоваться вызвать [systemClassInstance setDelegate: nil] в системных классах и другой код, который не скомпилирован с использованием ARC.


Для делегатов системных классов (NS *, UI *) вам необходимо использовать "старое" правило установки делегатов на нуль при освобождении объекта делегирования.

поэтому добавьте метод dealloc к вашему detailViewController

- (void)dealloc {
    self.mapView.delegate = nil;
}

Ответ 2

Хотя верно, что делегаты для таких классов должны быть явно установлены на nil, сделать это в dealloc уже слишком поздно. Вы уже потеряли ссылку на mapview во время viewDidUnload. Вы должны сделать self.mapView.delegate = nil ПЕРЕД viewDidUnload (возможно, viewWillDisappear или viewDidDisappear)

По моему опыту, ведут себя только MKMapView и UIWebView.