awakeFromNib,outlet和storyboards:文档错了吗?

Mar*_*ery 24 uikit iboutlet ios uistoryboard

根据NSObject UIKit Additions Reference,出口变量应该按照awakeFromNib调用的时间设置(强调所有我的):

nib加载基础结构将awakeFromNib消息发送到从nib归档重新创建的每个对象,但只有在归档中的所有对象都已加载并初始化之后.当一个对象收到一个awakeFromNib消息时,它保证已经建立了所有的插座和动作连接.

...

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

通常,您需要为需要在设计时无法完成的其他设置的对象实现awakeFromNib.例如,您可以使用此方法自定义任何控件的默认配置,以匹配用户首选项或其他控件中的值.您还可以使用它将单个控件还原到应用程序的某个先前状态.

但是,这与我的测试不符,至少使用Storyboard.以下测试的结果似乎与文档相矛盾:

  • 在Xcode中创建一个新的单视图应用程序.
  • 将第二个ViewController拖到故事板上.
  • 为第一个ViewController提供一个按钮,并从该按钮创建一个模式segue,显示第二个ViewController.
  • 为第二个ViewController创建一个ViewController类文件.
  • 在故事板上的第二个ViewController上创建一个标签,并创建一个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");
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 在模拟器中运行应用程序,然后单击按钮.

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

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
Run Code Online (Sandbox Code Playgroud)

由于这种行为的后果之一,当我需要我的ViewControllers有涉及它们的出口一些初始化的逻辑,我需要使用像在回答提出的一个黑客在这里,为了能够使用的插座.如果我正确理解文档,我被迫使用这个hack的事实是UIKit行为中的一个错误,我应该能够进行初始化awakeFromNib并简单地使用没有任何黑客的插座.

我在互联网上找不到任何其他关于这个问题的提及,这看起来很奇怪,因为这对我来说是一个至关重要的功能.我也从未使用过实际的nib文件,只使用了故事板,所以我对此缺少一些看法,关于这些内容的文档冗长而且很难以作为iOS的新手我不相信我已经理解了正确.这是一个真正的UIKit错误,还是我以某种方式误解了文档 - 也许这种方法甚至不打算与故事板一起使用?

rob*_*off 17

简答

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

您的视图控制器首先被加载并awakeFromNib在其nib加载时接收,但其视图层次结构nib尚未加载,因此awakeFromNib您不应该假设已经设置了视图层次结构的任何出口.

您的视图控制器viewDidLoad在其视图层次结构nib已加载后接收,因此viewDidLoad您可以假设已设置所有插座.

答案很长

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

:; 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
Run Code Online (Sandbox Code Playgroud)

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

:; 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
}
Run Code Online (Sandbox Code Playgroud)

如果场景具有故事板ID,或者segue连接到场景,或者它是初始场景,则场景仅显示在此列表中.

笔尖文件列出Info.plist包含这些视图控制器的视图层次.每个nib文件都包含其场景的视图控制器和场景中的任何其他顶级对象,但不包含视图控制器的视图或其任何子视图.

单独的nib文件包含场景的视图层次结构.视图层次结构nib的名称派生自视图控制器的对象ID及其顶级视图.您可以在Xcode的"Identity Inspector"中查看故事板中任何对象的对象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
Run Code Online (Sandbox Code Playgroud)

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

那么这与你的问题有什么关系呢?以下是:当您的应用程序从"故事板"加载场景时,它正在加载包含视图控制器(和其他顶级对象)的nib文件.当nib加载器完成加载该nib文件时,它会发送awakeFromNib到刚刚加载的所有对象.这包括你的视图控制器,但它并没有包括您的看法,因为你的意见在笔尖文件没有.

稍后,当要求视图控制器提供其view属性时,它会加载包含其视图层次结构的nib文件.视图控制器本身传递到-[UINib instantiateWithOwner:options:]作为owner参数.这就是nib加载器如何将视图层次结构中的对象连接到视图控制器的出口和操作.

当nib加载器完成加载视图层次结构nib时,它会发送awakeFromNib到刚刚加载的所有对象.由于您的视图控制器是不是一个这样的对象,你的视图控制器并没有收到awakeFromNib,此时消息.

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


nev*_*ing 4

视图控制器等到它们的视图被访问时才真正创建它们的视图。由于按钮位于视图控制器的视图中,因此它还不会被实例化。

  • 当然,我知道这似乎是实践中的行为 - 因此在执行视图初始化逻辑之前访问 ViewController 的“.view”属性的标准黑客。但这里的问题是,这是否是*应该*的情况,或者框架中的某个地方是否存在错误或错误文档。据我从文档中可以看出,我“应该”能够访问“ViewController”上的插座视图,而无需先访问其“.view”属性,而“这就是”这个问题的含义。 (3认同)