awakeFromNib,网点和故事板:是文件错误?

根据NSObjectUIKit Additions Reference ,outletvariables应该在awakeFromNib被调用的时候设置(强调全部是我的):

nib加载基础结构将一个awakeFromNib消息发送到从nib存档重新创build的每个对象,但仅在存档中的所有对象都已加载并初始化之后。 当一个对象收到一个awakeFromNib消息时,保证所有的出口和动作连接已经build立。

重要说明:由于无法保证从存档中实例化对象的顺序,因此您的初始化方法不应将消息发送到层次结构中的其他对象。 到其他对象的消息可以从awakeFromNib方法中安全地发送。

通常情况下,您需要为需要额外设置的对象实现awakeFromNib,而这在devise时是无法完成的。 例如,您可以使用此方法自定义任何控件的默认configuration,以匹配用户首选项或其他控件中的值。 您也可以使用它将个别控件恢复到应用程序的某个以前的状态。

但是,这不符合我的testing,至less使用故事板。 下面的testing结果似乎与文档相矛盾:

  • 在Xcode中创build一个新的单一视图应用程序。
  • 将第二个ViewController拖到故事板上。
  • 给第一个ViewController一个button,并从显示第二个ViewController的button创build一个模式的segue。
  • 为第二个ViewController创build一个ViewController类文件。
  • 在故事板上的第二个ViewController上创build一个标签,并从它创build一个名为someLabel的出口到相应的ViewController类。
  • 将下面的awakeFromNib实现添加到第二个ViewController中:

 - (void) awakeFromNib { [super awakeFromNib]; if (self.someLabel == nil) { NSLog(@"someLabel property is nil"); } else { NSLog(@"someLabel property is not nil"); } if (_someLabel == nil) { NSLog(@"_someLabel is nil"); } else { NSLog(@"_someLabel is not nil"); } } 
  • 在模拟器中运行应用程序,然后单击button。

当我这样做时,我观察到以下logging:

 2013-07-01 09:24:35.755 test[498:c07] someLabel property is nil 2013-07-01 09:24:35.758 test[498:c07] _someLabel is nil 

作为这种行为的结果,当我需要我的ViewController有一些初始化逻辑涉及到他们的网点,我需要使用一个像这里答案中提出的黑客入侵,以便能够使用sockets。 如果我正确地理解了文档,我被迫使用这个黑客攻击的事实是UIKit行为中的一个错误,我应该能够将这个初始化放在awakeFromNib并简单地使用这个sockets而不用任何黑客。

但是,在互联网上我找不到任何其他的提及这个问题,看起来很奇怪,因为这对我来说是一个基本上重要的function。 我也从来没有使用实际的笔尖文件,只有故事板,所以我错过了一些这方面的观点,关于这个东西的文档是冗长和困难的,作为iOS的新手我不知道我已经了解正确。 这是一个真正的UIKit错误,还是我以某种方式误解了文档 – 也许这种方法甚至不意味着与故事板结合使用?

简答

您的视图控制器及其视图层次结构是在运行时从单独的nib文件加载的。

你的视图控制器首先被加载,并且在加载它的nib时接收到awakeFromNib ,但是它的视图层次结构nib还没有被加载,所以在awakeFromNib你不应该假设已经设置了视图层次结构的任何出口。

您的视图控制器在其视图层次结构nib被加载后接收到viewDidLoad ,因此在viewDidLoad您可以假设所有的出口已经设置。

长答案

当Xcode构build您的应用程序时,它会编译您的故事板。 结果是包含一个包含一个Info.plist和一堆.nib文件的包(Finder作为一个文件夹)。 来自我的一个项目的示例:

 :; pwd /Users/mayoff/Library/<snip>/Pinner.app/Base.lproj/Main.storyboardc :; ll total 80 drwxr-xr-x 10 mayoff staff 340 May 11 22:13 ./ drwxr-xr-x 4 mayoff staff 136 May 11 22:13 ../ -rw-r--r-- 1 mayoff staff 1700 May 11 22:13 AccountCollection.nib -rw-r--r-- 1 mayoff staff 1110 May 11 22:13 AccountEditor.nib -rw-r--r-- 1 mayoff staff 2999 May 11 22:13 BYZ-38-t0r-view-8bC-Xf-vdC.nib -rw-r--r-- 1 mayoff staff 439 May 11 22:13 Info.plist -rw-r--r-- 1 mayoff staff 7621 May 11 22:13 LqH-9K-CeF-view-OwT-Ts-HoG.nib -rw-r--r-- 1 mayoff staff 6570 May 11 22:13 OZq-QF-pn5-view-xSR-gK-reL.nib -rw-r--r-- 1 mayoff staff 2473 May 11 22:13 UINavigationController-ZKB-z3-xgf.nib -rw-r--r-- 1 mayoff staff 847 May 11 22:13 UIPageViewController-ufv-JN-y6U.nib 

Info.plist将故事板中的场景名称映射到相应的笔尖:

 :; plutil -p Info.plist { "UIViewControllerIdentifiersToNibNames" => { "AccountCollection" => "AccountCollection" "UINavigationController-ZKB-z3-xgf" => "UINavigationController-ZKB-z3-xgf" "UIPageViewController-ufv-JN-y6U" => "UIPageViewController-ufv-JN-y6U" "AccountEditor" => "AccountEditor" } "UIStoryboardDesignatedEntryPointIdentifier" => "UINavigationController-ZKB-z3-xgf" "UIStoryboardVersion" => 1 } 

如果一个场景有一个故事板ID,或者一个segue连接到它,或者它是最初的场景,那么这个场景只会出现在这个列表中。

Info.plist的nib文件列表包含这些视图控制器的视图层次结构。 这些nib文件中的每一个都包含其场景的视图控制器以及场景中的任何其他顶级对象,但不包含视图控制器的视图或其任何子视图。

单独的nib文件包含场景的视图层次结构。 视图层次结构nib的名称从视图控制器的对象ID及其顶层视图派生而来。 您可以在Xcode中的“身份检查器”中看到故事板中任何对象的对象ID。 例如,我的“AccountCollection”场景的视图控制器的ID是BYZ-38-t0r ,其视图的ID是8bC-Xf-vdC ,所以场景的视图层次在文件BYZ-38-t0r-view-8bC-Xf-vdC.nib 。 场景nib文件包含其视图层次nib文件的名称:

 :; strings - AccountCollection.nib |grep -e '-.*-' UIPageViewController-ufv-JN-y6U BYZ-38-t0r-view-8bC-Xf-vdC <--------- UpstreamPlaceholder-5Hn-fK-fqQ UpstreamPlaceholder-8GL-mk-Rao q1g-aL-SLo.title 

如果一个场景没有视图层次结构,那么只有视图控制器的nib文件,而视图层次结构没有单独的nib文件。 例如, UIPageViewController场景在故事板中没有视图层次结构,所以没有对应于UIPageViewController-ufv-JN-y6U.nib视图层次结构nib。

那么这与你的问题有什么关系呢? 这是什么:当你的应用程序从“故事板”加载场景时,它将加载包含视图控制器(和其他顶级对象)的nib文件。 当nib加载器完成加载该nib文件时,它将awakeFromNib发送给它刚加载的所有对象。 这包括你的视图控制器,但是它不包含你的视图,因为你的视图不在那个nib文件中。

稍后,当您的视图控制器被要求其view属性,它加载包含其视图层次结构的nib文件。 视图控制器将自己传递给-[UINib instantiateWithOwner:options:]作为owner参数。 这就是nib加载器如何将视图层次结构中的对象连接到视图控制器的出口和动作。

当nib加载器完成加载视图层次结构nib时,它会将awakeFromNib发送给它所加载的所有对象。 由于您的视图控制器不是这些对象之一,您的视图控制器此时不会收到一个awakeFromNib消息。

instantiateWithOwner:options:返回时,视图控制器自己发送viewDidLoad消息。 这是您对视图层次结构进行更改的机会。

视图控制器等待直到他们的视图被访问实际上创build他们的视图。 由于该button位于视图控制器的视图中,因此它不会被实例化。