简体   繁体   English

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

[英]awakeFromNib, outlets and storyboards: is the documentation wrong?

According to the NSObject UIKit Additions Reference , outlet variables should be set by the time awakeFromNib is called (emphasis all mine): 根据NSObject UIKit Additions Reference ,应该在调用awakeFromNib设置出口变量(强调所有我的):

The nib-loading infrastructure sends an awakeFromNib message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. nib加载基础结构将awakeFromNib消息发送到从nib归档重新创建的每个对象,但只有在归档中的所有对象都已加载并初始化之后。 When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established. 当一个对象收到一个awakeFromNib消息时,它保证已经建立了所有的插座和动作连接。

... ...

Important: Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy. 要点:由于无法保证从归档实例化对象的顺序,因此初始化方法不应将消息发送到层次结构中的其他对象。 Messages to other objects can be sent safely from within an awakeFromNib method. 可以从awakeFromNib方法中安全地发送到其他对象的消息。

Typically, you implement awakeFromNib for objects that require additional set up that cannot be done at design time. 通常,您需要为需要在设计时无法完成的其他设置的对象实现awakeFromNib。 For example, you might use this method to customize the default configuration of any controls to match user preferences or the values in other controls. 例如,您可以使用此方法自定义任何控件的默认配置,以匹配用户首选项或其他控件中的值。 You might also use it to restore individual controls to some previous state of your application. 您还可以使用它将单个控件还原到应用程序的某个先前状态。

However, this does not match my tests, at least using Storyboards. 但是,这与我的测试不符,至少使用Storyboard。 The results of the following test seem to contradict the documentation: 以下测试的结果似乎与文档相矛盾:

  • Create a new Single View Application in Xcode. 在Xcode中创建一个新的单视图应用程序。
  • Drag a second ViewController onto the storyboard. 将第二个ViewController拖到故事板上。
  • Give the first ViewController a button, and create a modal segue from that button that displays the second ViewController. 为第一个ViewController提供一个按钮,并从该按钮创建一个模式segue,显示第二个ViewController。
  • Create a ViewController class file for the second ViewController. 为第二个ViewController创建一个ViewController类文件。
  • Create a label on the second ViewController on the storyboard and create an outlet called someLabel from it to the corresponding ViewController class. 在故事板上的第二个ViewController上创建一个标签,并从它创建一个名为someLabel的插座到相应的ViewController类。
  • Add the following awakeFromNib implementation to the second 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 the app in the simulator and click the button. 在模拟器中运行应用程序,然后单击按钮。

When I do this, I observe the following logged: 当我这样做时,我观察到以下记录:

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

As a consequence of this behaviour, when I need my ViewControllers to have some initialisation logic that involves their outlets, I need to use a hack like the one proposed in the answer here in order to be able to use the outlets. 由于这种行为的后果之一,当我需要我的ViewControllers有涉及它们的出口一些初始化的逻辑,我需要使用像在回答提出的一个黑客在这里 ,为了能够使用的插座。 If I'm understanding the documentation correctly, the fact that I'm forced to use this hack is a bug in the UIKit behaviour, and I ought to be able to put that initialisation in awakeFromNib and simply use the outlets without any hacks. 如果我正确理解文档,我被迫使用这个hack的事实是UIKit行为中的一个错误,我应该能够将该初始化放在awakeFromNib并且只需使用没有任何黑客的插座。

I can't find any other mention of this issue on the internet, though, which seems odd given what a fundamentally important bit of functionality this appears (to me) to be. 我在互联网上找不到任何其他关于这个问题的提及,这看起来很奇怪,因为这对我来说是一个至关重要的功能。 I've also never used actual nib files, only storyboards, so I'm missing some perspective on this, and the documentation on this stuff is verbose and difficult enough that as a newbie to iOS I'm not confident that I've understood correctly. 我也从未使用过实际的nib文件,只使用了故事板,所以我对此缺少一些看法,关于这些内容的文档冗长而且很难,作为iOS的新手,我不相信我已经理解了正确。 Is this a genuine UIKit bug, or have I misunderstood the documentation in some way - perhaps this method isn't even meant to be used in conjunction with storyboards? 这是一个真正的UIKit错误,还是我以某种方式误解了文档 - 也许这种方法甚至不打算与故事板一起使用?

Short Answer 简答

Your view controller and its view hierarchy are loaded from separate nib files at runtime. 视图控制器及其视图层次结构在运行时从单独的nib文件加载。

Your view controller is loaded first and receives awakeFromNib when its nib is loaded, but its view hierarchy nib hasn't been loaded yet, so in awakeFromNib you shouldn't assume any outlets to the view hierarchy have been set yet. 首先加载视图控制器并在加载其nib时接收awakeFromNib ,但其视图层次结构nib尚未加载,因此在awakeFromNib您不应假设已设置任何视图层次结构的出口。

Your view controller receives viewDidLoad after its view hierarchy nib has been loaded, so in viewDidLoad you can assume all outlets have been set. 视图控制器在加载视图层次结构nib后接收viewDidLoad ,因此在viewDidLoad您可以假设已设置所有出口。

Long Answer 答案很长

When Xcode builds your app, it compiles your storyboard. 当Xcode构建您的应用程序时,它会编译您的故事板。 The result is a package (a folder that Finder treats as a file) containing an Info.plist and a bunch of .nib files. 结果是一个包(Finder视为文件的文件夹),包含Info.plist和一堆.nib文件。 Example from one of my projects: 我的一个项目示例:

:; 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

The Info.plist maps the scene names in your storyboard to the corresponding nibs: 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
}

A scene only shows up in this list if it has a storyboard ID, or a segue connects to it, or it is the initial scene. 如果场景具有故事板ID,或者segue连接到场景,或者它是初始场景,则场景仅显示在此列表中。

The nib files list in Info.plist do not contain the view hierarchies of those view controllers. Info.plist的nib文件列表包含这些视图控制器的视图层次结构。 Each of those nib files contains the view controller of its scene and any other top-level objects in the scene, but not the view controller's view or any of its subviews. 每个nib文件都包含其场景的视图控制器和场景中的任何其他顶级对象,但不包含视图控制器的视图或其任何子视图。

A separate nib file contains the view hierarchy for the scene. 单独的nib文件包含场景的视图层次结构。 The name of view hierarchy nib derived from the object IDs of the view controller and its top-level view. 视图层次结构nib的名称派生自视图控制器的对象ID及其顶级视图。 You can see the object ID of any object in your storyboard in the “Identity Inspector” in Xcode. 您可以在Xcode的“Identity Inspector”中查看故事板中任何对象的对象ID。 For example, my “AccountCollection” scene's view controller's ID is BYZ-38-t0r and its view's ID is 8bC-Xf-vdC , so the view hierarchy for the scene is in file BYZ-38-t0r-view-8bC-Xf-vdC.nib . 例如,我的“AccountCollection”场景的视图控制器的ID是BYZ-38-t0r ,其视图的ID是8bC-Xf-vdC ,因此场景的视图层次结构位于文件BYZ-38-t0r-view-8bC-Xf-vdC.nib The scene nib file contains the name of its view hierarchy nib file: 场景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

If a scene doesn't have a view hierarchy, then there will just be a nib file for the view controller and no separate nib file for the view hierarchy. 如果场景没有视图层次结构,那么视图控制器只会有一个nib文件,视图层次结构中没有单独的nib文件。 For example, a UIPageViewController scene doesn't have a view hierarchy in the storyboard so there's no view hierarchy nib corresponding to UIPageViewController-ufv-JN-y6U.nib . 例如, UIPageViewController场景在故事板中没有视图层次结构,因此没有与UIPageViewController-ufv-JN-y6U.nib对应的视图层次结构nib。

So what does all this have to do with your question? 那么这与你的问题有什么关系呢? Here's what: when your app loads a scene from the “storyboard”, it's loading the nib file containing the view controller (and other top-level objects). 以下是:当您的应用程序从“故事板”加载场景时,它正在加载包含视图控制器(和其他顶级对象)的nib文件。 When the nib loader finishes loading that nib file, it sends awakeFromNib to all the objects it just loaded. 当nib加载器完成加载该nib文件时,它会将awakeFromNib发送到刚刚加载的所有对象。 This includes your view controller, but it does not include your views, because your views weren't in that nib file. 这包括你的视图控制器,但它包括你的意见,因为你的意见在笔尖文件没有。

Later, when your view controller is asked for its view property, it loads the nib file containing its view hierarchy. 稍后,当您的视图控制器被要求提供其view属性时,它会加载包含其视图层次结构的nib文件。 The view controller passes itself to -[UINib instantiateWithOwner:options:] as the owner argument. 视图控制器将自身传递给-[UINib instantiateWithOwner:options:]作为owner参数。 This is how the nib loader can connect objects in the view hierarchy to the view controller's outlets and actions. 这就是nib加载器如何将视图层次结构中的对象连接到视图控制器的出口和操作。

When the nib loader finishes loading the view hierarchy nib, it sends awakeFromNib to all the objects it just loaded. 当nib加载器完成加载视图层次结构nib时,它会将awakeFromNib发送到刚刚加载的所有对象。 Since your view controller was not one of those objects, your view controller does not receive an awakeFromNib message at this time. 由于您的视图控制器不是一个这样的对象,你的视图控制器收到awakeFromNib此时消息。

When instantiateWithOwner:options: returns, the view controller sends itself the viewDidLoad message. instantiateWithOwner:options:返回时,视图控制器自己发送viewDidLoad消息。 This is your opportunity to make changes to the view hierarchy. 这是您更改视图层次结构的机会。

View controllers wait until their view is accessed to actually create their view. 视图控制器等到访问其视图以实际创建其视图。 Since the button is in the view controller's view, it won't be instantiated yet. 由于该按钮位于视图控制器的视图中,因此它不会被实例化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM