UISearchDisplayController вызывает сбой после viewDidUnload

У меня есть проект, использующий StoryBoards и UISearchDisplayController, используемый в контексте UINavigationController, который появляется в корневом контроллере представления. Когда я помещаю новый контроллер представления в стек и вызываю симулированное предупреждение о памяти (или фактически получаю предупреждение о нехватке памяти). Предыдущий контроллер представления успешно выгружает свое представление. Однако, когда я извлекаю второй контроллер представления из стека, я получаю EXC_BAD_ACCESS. Я включил NSZombies и обнаружил это:

[UISearchDisplayController сохранить]: сообщение отправлено освобожденному экземпляру 0xb13aa30

Я не (по крайней мере, в моем коде) отправляю это сообщение в UISearchDisplayController. Программно говоря, я ничего с ним не делаю. Точки останова показывают, что я даже не попал в viewDidLoad первого контроллера представления.

Однако кое-что любопытное: ради смеха и хихиканья я решил полностью retain SDC в моем viewDidLoad, просто чтобы посмотреть, что произойдет, и не произойдет ли сбой. Однако мой экземпляр UISearchDisplayController - это nil.

Я сделал обратную трассировку и получил этот вывод:

#0  0x01e30e1e in ___forwarding___ ()
#1  0x01e30ce2 in __forwarding_prep_0___ ()
#2  0x01dd1490 in CFRetain ()
#3  0x01eb69c0 in +[__NSArrayI __new::] ()
#4  0x01e0a00a in -[__NSPlaceholderArray initWithObjects:count:] ()
#5  0x01e34f52 in +[NSArray arrayWithObjects:count:] ()
#6  0x01e5e084 in -[NSDictionary allValues] ()
#7  0x01035272 in -[UINib instantiateWithOwner:options:] ()
#8  0x00edce2c in -[UIViewController _loadViewFromNibNamed:bundle:] ()
#9  0x00edd3a9 in -[UIViewController loadView] ()
#10 0x00edd5cb in -[UIViewController view] ()
#11 0x00edd941 in -[UIViewController contentScrollView] ()
#12 0x00eef47d in -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] ()
#13 0x00eef66f in -[UINavigationController _layoutViewController:] ()
#14 0x00eef93b in -[UINavigationController _startTransition:fromViewController:toViewController:] ()
#15 0x00ef03df in -[UINavigationController _startDeferredTransitionIfNeeded] ()
#16 0x00ef16cb in _popViewControllerNormal ()
#17 0x00ef196c in -[UINavigationController _popViewControllerWithTransition:allowPoppingLast:] ()
#18 0x0b446e82 in -[UINavigationControllerAccessibility(SafeCategory) _popViewControllerWithTransition:allowPoppingLast:] ()
#19 0x00ef0b10 in -[UINavigationController popViewControllerAnimated:] ()
#20 0x00ef297d in -[UINavigationController navigationBar:shouldPopItem:] ()
#21 0x00e7dabe in -[UINavigationBar _popNavigationItemWithTransition:] ()
#22 0x00e7da49 in -[UINavigationBar popNavigationItemAnimated:] ()
#23 0x0b42208c in -[UINavigationBarAccessibility(SafeCategory) popNavigationItemAnimated:] ()
#24 0x00e80507 in -[UINavigationBar _handleMouseUpAtPoint:] ()
#25 0x00e8074c in -[UINavigationBar touchesEnded:withEvent:] ()
#26 0x00e3fa30 in -[UIWindow _sendTouchesForEvent:] ()
#27 0x00e3fc56 in -[UIWindow sendEvent:] ()
#28 0x00e26384 in -[UIApplication sendEvent:] ()
#29 0x00e19aa9 in _UIApplicationHandleEvent ()
#30 0x02d37fa9 in PurpleEventCallback ()
#31 0x01e9e1c5 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
#32 0x01e03022 in __CFRunLoopDoSource1 ()
#33 0x01e0190a in __CFRunLoopRun ()
#34 0x01e00db4 in CFRunLoopRunSpecific ()
#35 0x01e00ccb in CFRunLoopRunInMode ()
#36 0x02d36879 in GSEventRunModal ()
#37 0x02d3693e in GSEventRun ()
#38 0x00e17a9b in UIApplicationMain ()
#39 0x00002b72 in main (argc=1, argv=0xbffff620)

Кажется, там нет ничего действительно интересного (есть ли когда-нибудь? :P), и, похоже, это все внутренности Apple. Любые идеи о том, как заставить эту проблему уйти?

ОБНОВЛЕНИЕ: даже когда я удаляю связь между моим контроллером представления и свойством контроллера отображения поиска, но создаю для него собственный IBOutlet, он все равно дает сбой. Может плохой баг?

ОБНОВЛЕНИЕ 2: когда я программно создаю собственный экземпляр UISearchDisplayController (не через раскадровку) и создаю его в viewDidLoad, все работает так, как должно.

ОБНОВЛЕНИЕ 3: я могу постоянно воспроизводить эту проблему в новом проекте с раскадровкой. Я сделал то же самое с ванильным пером, и все заработало так, как предполагалось. Однако, если я настрою то же самое, используя раскадровку и переход, он взорвется, как в моем реальном проекте. :(

RECAP. Вот шаги по воссозданию этой проблемы:

  1. Создайте контроллер представления в раскадровке с помощью UISearchDisplayController
  2. Вставьте новый контроллер представления в стек навигации.
  3. Причина предупреждения о нехватке памяти
  4. Вытолкнуть контроллер из стека
  5. КАБУМ!

В этот момент viewDidLoad даже не вызывается на первом контроллере представления, код Apple взрывается до этого.


person Wayne Hartman    schedule 19.12.2011    source источник
comment
Как выглядит ваш метод viewDidUnload?   -  person aopsfan    schedule 20.12.2011
comment
@aopsfan Просто обнуляю мой IBOutlets и вызываю реализацию моего супера viewDidUnload.   -  person Wayne Hartman    schedule 20.12.2011
comment
что произойдет, если вы удалите этот код?   -  person aopsfan    schedule 20.12.2011
comment
Я понимаю, что это может быть утечка, но просто проверить...   -  person aopsfan    schedule 20.12.2011
comment
Вы установили IBOutlet на слабый (ARC) или назначили (без ARC)?   -  person    schedule 13.01.2012
comment
@relikd Это приложение не для ARC.   -  person Wayne Hartman    schedule 14.01.2012
comment
У меня такая же проблема с АРК. Если вы используете UITableViewController, вам не нужно устанавливать явный IBOutlet — раскадровка автоматически устанавливает для него значение self.searchDisplayController.   -  person Harpastum    schedule 14.01.2012
comment
Мой XCode продолжает падать, когда я помещаю компонент в контроллер представления... Первое впечатление от раскадровки для меня   -  person tia    schedule 17.01.2012
comment
Почему бы вам не отправить электронное письмо в службу технической поддержки разработчиков Apple и не посмотреть, что они порекомендуют в то же время, поскольку именно они создали проблему в первую очередь.   -  person SimplyKiwi    schedule 17.01.2012
comment
@iBrad Apps - я опубликовал отчет об ошибке Radar. Моя вера в форумы разработчиков Apple не очень сильна, настолько, что я даже не думаю, что стоит тратить время на публикацию там — серьезно.   -  person Wayne Hartman    schedule 17.01.2012
comment
Nono — техническая поддержка разработчиков Apple. Это не форум. Здесь вы получаете один на один помощь с разработчиком Apple по любым вопросам, которые у вас есть. Вы получаете 2 запроса в год.   -  person SimplyKiwi    schedule 17.01.2012
comment
Я хотел бы продублировать это, что у вас за проблема с радаром #?   -  person Steven Fisher    schedule 14.08.2012


Ответы (6)


Вот что я сделал (конечно, это обходной путь, а не исправление ошибки Apple):

Во-первых, в базе UIViewController я создал свойство с именем searchController:

@property (nonatomic, retain) IBOutlet UISearchDisplayController* searchController;

Я добавил UISearchBar через построитель интерфейса, чтобы у меня был заполнитель в моем пользовательском интерфейсе для него. Затем в моем viewDidLoad я вручную настраиваю контроллер и подключаю его:

UISearchDisplayController* searchController = [[UISearchDisplayController alloc] 
                             initWithSearchBar:self.searchBar contentsController:self];
searchController.searchResultsDataSource = self;
searchController.searchResultsDelegate = self;
searchController.delegate = self;

self.searchController = searchController;
[searchController release];

В моем viewDidUnload я обязательно удалю это:

self.searchController = nil;
person Wayne Hartman    schedule 13.01.2012
comment
Вам, вероятно, не нужно обнулять весь searchController, просто установите его delegate равным нулю. - person Mark Adams; 14.01.2012
comment
@Mark Adams - Поскольку я retain использую searchController в свойстве, я nil вывожу его. Объекты, которые можно легко воссоздать в viewDidLoad, должны быть обнулены в viewDidUnload. - person Wayne Hartman; 14.01.2012
comment
Справедливо. Я имел в виду вашу конкретную аварию. - person Mark Adams; 14.01.2012
comment
Я не понимаю, как вы назначаете только что созданный экземпляр searchController свойству self.searchDisplayController, которое доступно только для чтения. - person John Topley; 20.04.2012
comment
@JohnTopley - Извиняюсь. Я обновил свой пример кода, чтобы указать на свойство, которое я создал в своем подклассе UIViewController. - person Wayne Hartman; 20.04.2012
comment
Можете ли вы немного расширить это? Я не совсем понимаю, что ты делаешь. - person Steven Fisher; 15.08.2012
comment
На самом деле я прочитал ответ Уэйна Лю, и он отлично сработал для меня. Ваш, вероятно, имел бы смысл в среде MRR, но я перешел к ARC. - person Steven Fisher; 15.08.2012
comment
Согласно документации, метод viewDidUnload устарел и никогда не вызывается. - person Vincent Tourraine; 03.04.2014
comment
@VincentTourraine Вздох. Пожалуйста, примите во внимание дату публикации и дату, когда viewDidUnload официально стало устаревшим. - person Wayne Hartman; 03.04.2014
comment
@WayneHartman Просто для справки в будущем, я считаю, что полезно указать на это. - person Vincent Tourraine; 09.04.2014

До сих пор я нашел это рабочее решение для iOS 5 SDK с использованием ARC:

в файле .h объявите собственное свойство searchDisplayController с помощью IBOutlet

@property (strong, nonatomic) IBOutlet UISearchDisplayController * searchDisplayController;

Затем в файле .m синтезируйте его:

@synthesize searchDisplayController;

Но не устанавливайте его равным нулю в viewDidUnload.

Так что контроллер отображения поиска будет использовать созданное вами свойство, а не унаследованное свойство.

Я также заметил, что аналогичная ошибка также появляется для распознавателей жестов (если вы создаете распознаватели жестов из раскадровки, а не создаете их программно). Нам также нужно создать СИЛЬНЫЕ свойства распознавателя жестов и связать их с объектами распознавателя жестов, которые вы создаете в раскадровке. Затем в viewDidUnload не устанавливайте их равными нулю. ‹-- это предотвращает сбои.

person Wayne Liu    schedule 24.05.2012
comment
Круто, это сработало для меня. Это сэкономило мне часы (хотя я уже потратил некоторые из них, чтобы добраться до этого момента;)) - person Holtwick; 07.06.2012

Почему вы не используете только self.searchDisplayController?

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

person Mrunal    schedule 14.01.2012

@Wayne: я столкнулся с той же проблемой с SearchDisplayController, созданным из раскадровки, и потратил более дня, пытаясь отладить сбой, который, казалось, возникал, когда ни один из моих кодов не выполнялся. В моем случае симптом заключался в том, что пользователь нажимал на вкладку в UITabBarController, чтобы вернуться к ViewController, который был выгружен после предупреждения о памяти. Метод viewDidLoad выгруженного контроллера представления никогда не запускается, и код достигает по крайней мере табуляции tabBarController:didSelectViewController: (которая должна выполняться после viewDidLoad), прежде чем произойдет сбой где-нибудь в коде сборки!

Большое спасибо за публикацию этого обходного пути и за все последующие действия. Небольшое улучшение заключается в перемещении экземпляра UIDisplayController в лениво загружаемый метод доступа для свойства searchDisplayController. Практический эффект незначителен, но выглядит приятнее!

person MpK    schedule 27.03.2012

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

if (self.searchDisplayController.active) {

        [self.searchDisplayController setActive:NO];

}
person Sammio2    schedule 29.07.2013

ViewDidUnload Called означает, что в вашем приложении есть исключения памяти.

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

Поскольку кажется, что в коде нет ничего плохого, когда возникают исключения памяти, лучше перейти на предыдущий экран пользователя, сказав [self.navigationController popViewControllerAnimated:NO]

person Sanjeev Rao    schedule 16.01.2012
comment
В какой-то момент на чьем-то устройстве неизбежно возникнет ситуация, когда на нем законно закончится память, причем не по моей вине как разработчика. Смысл этого вопроса в том, чтобы избежать сбоя, вызванного ошибкой Apple. Вызов [self.navigationController popViewControllerAnimated:NO] в этой ситуации бесполезен, так как это один из шагов воссоздания проблемы. - person Wayne Hartman; 16.01.2012