24

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

nib 加载基础架构向从 nib 存档重新创建的每个对象发送 awakeFromNib 消息,但前提是存档中的所有对象都已加载和初始化。当一个对象收到一个 awakeFromNib 消息时,它保证已经建立了它的所有出口和动作连接。

...

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

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

但是,这与我的测试不匹配,至少使用 Storyboards。以下测试的结果似乎与文档相矛盾:

  • 在 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");
    }
}
  • 在模拟器中运行应用程序并单击按钮。

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

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

由于这种行为,当我需要我的 ViewControllers 具有一些涉及其插座的初始化逻辑时,我需要使用类似于此处答案中提出的黑客攻击,以便能够使用插座。如果我正确理解了文档,那么我被迫使用这个 hack 的事实是 UIKit 行为中的一个错误,我应该能够将该初始化放入awakeFromNib并简单地使用插座而无需任何 hack。

不过,我在互联网上找不到任何其他提及此问题的内容,考虑到(对我而言)这是一项非常重要的功能,这似乎很奇怪。我也从未使用过实际的 nib 文件,只有故事板,所以我对此缺乏一些看法,而且关于这些东西的文档非常冗长和困难,以至于作为 iOS 的新手,我不相信我已经理解正确。这是一个真正的 UIKit 错误,还是我以某种方式误解了文档 - 也许这种方法甚至不打算与情节提要一起使用?

4

2 回答 2

18

简答

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

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

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

长答案

当 Xcode 构建您的应用程序时,它会编译您的故事板。Info.plist结果是一个包含一个和一堆文件的包(Finder 将其视为文件的文件夹).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

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
}

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

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

一个单独的 nib 文件包含场景的视图层次结构。视图层次结构 nib 的名称源自视图控制器及其顶级视图的对象 ID。您可以在 Xcode 的“身份检查器”中查看故事板中任何对象的对象 ID。例如,我的“AccountCollection”场景的视图控制器 ID 为BYZ-38-t0r,其视图 ID 为8bC-Xf-vdC,因此该场景的视图层次结构在 file 中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 文件时,它会发送awakeFromNib到它刚刚加载的所有对象。这包括您的视图控制器,但包括您的视图,因为您的视图不在该 nib 文件中。

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

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

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

于 2016-05-12T15:29:08.413 回答
4

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

于 2013-07-01T09:06:58.333 回答