viewDidLoad вызывается дважды в RootViewController при запуске
-
22-08-2019 - |
Вопрос
Кто-нибудь знает, почему этот корень View Controller's
viewDidLoad
вызывается ли дважды при запуске?Это сводит меня с ума!
вот трассировка стека с первого раза до конца viewDidLoad
:
#0 0x0000276a in -[RootViewController viewDidLoad] at RootViewController.m:71
#1 0x3097548f in -[UIViewController view]
#2 0x00002734 in -[RootViewController initWithCoder:] at RootViewController.m:39
#3 0x30ab5ce4 in -[UIClassSwapper initWithCoder:]
#4 0x30514636 in _decodeObjectBinary
#5 0x30514035 in _decodeObject
#6 0x30ab5a1d in -[UIRuntimeConnection initWithCoder:]
#7 0x30514636 in _decodeObjectBinary
#8 0x30515f27 in -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:]
#9 0x305163b0 in -[NSArray(NSArray) initWithCoder:]
#10 0x30514636 in _decodeObjectBinary
#11 0x30514035 in _decodeObject
#12 0x30ab4dde in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:]
#13 0x30ab6eb3 in -[NSBundle(NSBundleAdditions) loadNibNamed:owner:options:]
#14 0x308f85f1 in -[UIApplication _loadMainNibFile]
#15 0x30901a15 in -[UIApplication _runWithURL:sourceBundleID:]
#16 0x308fef33 in -[UIApplication handleEvent:withNewEvent:]
#17 0x308fad82 in -[UIApplication sendEvent:]
#18 0x309013e1 in _UIApplicationHandleEvent
#19 0x32046375 in PurpleEventCallback
#20 0x30245560 in CFRunLoopRunSpecific
#21 0x30244628 in CFRunLoopRunInMode
#22 0x308f930d in -[UIApplication _run]
#23 0x309021ee in UIApplicationMain
#24 0x000022e4 in main at main.m:14
и во второй раз:
#0 0x0000276a in -[RootViewController viewDidLoad] at RootViewController.m:71
#1 0x30ab50cd in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:]
#2 0x30ab6eb3 in -[NSBundle(NSBundleAdditions) loadNibNamed:owner:options:]
#3 0x308f85f1 in -[UIApplication _loadMainNibFile]
#4 0x30901a15 in -[UIApplication _runWithURL:sourceBundleID:]
#5 0x308fef33 in -[UIApplication handleEvent:withNewEvent:]
#6 0x308fad82 in -[UIApplication sendEvent:]
#7 0x309013e1 in _UIApplicationHandleEvent
#8 0x32046375 in PurpleEventCallback
#9 0x30245560 in CFRunLoopRunSpecific
#10 0x30244628 in CFRunLoopRunInMode
#11 0x308f930d in -[UIApplication _run]
#12 0x309021ee in UIApplicationMain
#13 0x000022e4 in main at main.m:14
Решение
Странно.Я не видел этого конкретного случая, но в целом вы должны предположить, что viewDidLoad может быть вызван несколько раз.Он будет вызываться всякий раз, когда загружается файл nib, который ссылается на этот контроллер.
Для простого приложения только с одним наконечником этого не должно произойти.Но в более сложном приложении, которое может загружать и выгружать контроллеры просмотра, это происходит постоянно.
Другие советы
У меня была такая же проблема при первом запуске моего приложения.Что я обнаружил, так это то, что в моем файле MainWindow.xib я устанавливал оба делегата моего приложения viewController
розетка, и мое окно rootViewController
подключитесь к моему корневому контроллеру просмотра.Когда вы создаете файл проекта на основе представления в Xcode, делегат вашего приложения didFinishLaunchingWithOptions
будет предварительно заполнен:
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
Я верю, что self.viewController
ivar создается из MainWindow.xib перед didFinishLaunchingWithOptions
ему звонят.Затем предварительно заполненный код, приведенный выше, задает размер окна rootViewController
.Таким образом, если в сочетании вы укажете rootViewController
выход для окна в вашем файле MainWindow.xib, ваш корневой контроллер просмотра фактически будет создан дважды и два раза добавлен в качестве корневого контроллера просмотра окна.
Я провел некоторую отладку, и вот что я нашел о ViewController
порядок загрузки:
initWithNibName:bundle: self = <original instance>, retainedOutlet = 0x0
loadView >>> self = <original instance>, retainedOutlet = 0x0
initWithCoder: self = <coder instance>, retainedOutlet = 0x0
initWithCoder: self = <coder instance>, retainedOutlet = 0x0
setView: self = <original instance>, retainedOutlet = 0x0
setRetainedOutlet: self = <original instance>, retainedOutlet = 0x1613c40
viewDidLoad self = <coder instance>, retainedOutlet = 0x0
awakeFromNib self = <coder instance>, retainedOutlet = 0x0
loadView <<<
viewDidLoad self = <original instance>, retainedOutlet = 0x1613c40
viewWillAppear: self = <original instance>, retainedOutlet = 0x1613c40
dealloc self = <coder instance>, retainedOutlet = 0x0
viewDidAppear: self = <original instance>, retainedOutlet = 0x1613c40
Во время метода loadView, initWithCoder:
вызывается и новая копия viewController
создается.это то, что передается в несколько методов (например viewDidLoad
).копия будет уничтожена позже при вызове dealloc.хорошей новостью является то, что в этой копии сохраненные выходы не настроены, поэтому вы можете использовать это как тест, чтобы узнать, следует ли вам инициализировать переменные, вызывать другие методы и, что наиболее важно, следует ли вам освобождать и уничтожать объекты во время освобождения.
Ключ на вынос:настоящий viewController
будет иметь свое сохраненное IBOutlet
свойства настроены.если вы используете переопределенный метод, который вызывается несколько раз, просто проверьте один из ваших сохраненных IBOutlet
свойства для NULL
.если они есть NULL
, затем немедленно возвращайтесь.
У кого-нибудь есть какие-нибудь подсказки относительно того, почему это происходит таким образом?
Побочный эффект этого:вы не можете использовать awakeFromNib
надежно.
Вы не можете предполагать, что viewDidLoad будет вызван только один раз.Если вы инициализируете объекты и хотите получить гарантию, выполните инициализацию либо в методе init, либо при загрузке из файла nib с помощью метода awakeFromNib.
У меня была похожая проблема, и это было результатом переименования моего XIB-файла и его ViewController
класс (Владелец файла).Не делайте этого - поскольку из-за этого действительно были неправильно определены представления и делегаты внутри XML, и это было невозможно восстановить.Между тем, у меня была ссылка на загрузку исходного VC, который должен был стать моим новым VC.Я полагаю, что это привело к воссозданию родительского элемента, а затем VC, который я действительно пытался вызвать.По сути, я создал косвенную рекурсию к VC, которая имеет x2 viewDidLoad
записи в моем следе.
Я не думаю, что есть какая-то веская причина для x2 viewDidLoad
поскольку это genesis и может вызывать другую инициализацию с неправильными предполагаемыми предварительными условиями.Каждый раз, когда я видел x2 viewDidLoad, это была ошибка кодирования с моей стороны - довольно часто, когда я проводил рефакторинг и перемещал классы VC.
Если есть уважительная причина для более чем на viewDidLoad
позвоните, пожалуйста, кому-нибудь (разработчик Apple, вы слушаете), объясните это в технических деталях -- Я искал этот ответ уже несколько месяцев.
У меня была эта проблема, но я смог ее исправить.
Решение:
Переименуйте класс контроллера представления, который загружается дважды.
Подробные сведения:
Переименуйте его и сделайте новое название чем-то совершенно новым. Переименование файла не останавливает проблему с двойной загрузкой.Создание нового проекта (как предлагали другие) может оказаться излишеством, по крайней мере, сначала попробуйте более простые решения!Переименуйте класс целевого VC.
Подсказка:Если переименование класса устраняет вашу проблему, вам, очевидно, придется обновить все ваши ссылки на этот класс.Вы можете ускорить это, используя Command + Shift + F для поиска по всему проекту.
Я столкнулся с той же проблемой, когда переделывал ViewController
с нуля, чтобы избавиться от файла XIB и сделать класс многоразовым.У меня была эта секунда ViewController
экземпляр, который получил бы viewDidLoad
сообщение, за которым следует сообщение об освобождении.
Я узнал, что это было результатом loadView
метод, который не был переопределен в ViewController
.Значение по умолчанию loadView
вызванный awakeFromNib
, с помощью nibName
свойство, установленное на имя класса.Несмотря на то, что я удалил XIB-файл из проекта, он все еще находился в каталоге приложения симулятора.
Таким образом, несмотря на то, что вы могли бы просто сбросить содержимое и настройки симулятора, чтобы избавиться от второго viewDidLoad
, лучшим способом может быть просто переопределение loadView
вот так:
- (void)loadView {
self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
self.view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
}
Это имеет смысл, если вы рассмотрите документацию для UIViewController's
просмотр свойства:
Если вы получаете доступ к этому свойству и его значение в данный момент равно нулю, контроллер представления автоматически вызывает
loadView
метод и возвращает результирующий вид.Значение по умолчаниюloadView
метод пытается загрузить представление из файла nib, связанного с представлением контроллера (если таковой имеется).Если ваш view controller не имеет связанного файла nib, вам следует переопределитьloadView
метод и используйте его для создания корневого представления и всех его вложенных представлений.
В моем случае я не заметил, что на самом деле я дважды назначил RootViewController в:
application:didFinishLaunchingWithOptions:
и applicationDidBecomeActive:
Просто чтобы добавить к этому, если вы используете системную функцию, такую как TouchID, то Приложение будет сигнализировать о своей активности в вашем AppDelegate будет вызван, и если вы, скажем, сбрасываете контроллеры на безопасный корневой контроллер, то вы будете повторно вызваны, и Выполняет seguewithidentifier(self.MAIN_SEGUE ,отправитель:самость) не будет стрелять!
Это случилось со мной, когда я объединил проект из раскадровки со старым способом, используя xibs для построения представлений.Основной причиной обратного переключения был тот факт, что я не смог должным образом настроить модальное представление.Обычно я делаю это, используя метод делегирования из UIButton, создающий экземпляр определенного viewcontroller, устанавливающий некоторые из его свойств (наиболее важным из которых является делегат, чтобы я мог снова правильно отключить модальный контроллер представления), а затем представляющий его модальным способом.В новом способе раскадровки это предположительно делается с помощью перехода.Настройка перехода выполнима только путем создания пользовательского класса, который расширяет класс UIStoryboardSegue.Я нахожу этот способ слишком сложным по сравнению с тем, как это было раньше, поэтому я слился обратно.
Как это привело к тому, что я дважды загрузил viewcontroller?При переносе кода из проекта storyboard в проект xib я создал пару xibs (по одному для каждого ViewController) и скопированный объект viewcontroller из раскадровки.Это привело к созданию xib с не viw, а viewcontroller;это означает, что я поместил viewcontroller в viewcontroller (поскольку владелец файла также является экземпляром viewcontroller).Я не думаю, что в вашем случае у вас была эта проблема, но я надеюсь, что, возможно, когда-нибудь это кому-нибудь поможет.
Чтобы исправить это, переместите представление из контроллера представления из контроллера представления на корневой уровень раздела объектов.И контроллер представления, и его элемент навигации должны быть удалены.Создайте и запустите, и вы должны увидеть только одно выделение для контроллера представления.Это владелец файла.
Что, если ваш код обращался к свойству view, когда оно еще не загружено, контроллер представления создаст просто пустое представление, и это может вызвать view did load
случайно.
Наиболее распространенной ошибкой является доступ к свойству view во время инициализации.Может быть, какой-то инструмент доступа к свойству (setter), который вызывается xib, должен случайно получить доступ к свойству view.
Что делать, если какое-то свойство помечено IBInspectable
вы должны были бы проверить isViewLoaded
прежде чем применить какое-либо значение к просмотру.
-(void) setSomeProperty:(UIColor*) someColor
{
_someColor = someColor;
if(self.isViewLoaded) {
// self.view causes view creation and invokes 'viewDidLoad' then the view is not ready yet.
self.view.backgroundColor = someColor;
}
}
-(void) viewDidLoad
{
[super viewDidLoad]
if(_someColor){
self.view.backgroundColor = _someColor;
}
}