连接指的是nib文件中的操作。它联合了两个nib对象,从一个运行到另一个。连接有个方向:这也是我为什么要用“从哪里到哪里”来描述它。这两个对象叫作连接的源与目标。
有两种类型的连接,插座变量连接与动作连接。本节后面的内容将会介绍它们、如何创建和配置,同时还会介绍它们所解决的问题的本质。
7.3.1 插座变量
nib加载并且其实例生成时会产生一个问题:如果没有引用它们,那么这些实例就是无用的。7.2节中,我们是通过捕获nib加载时所实例化的顶层对象数组来解决这个问题的。不过还有另外一种方式:使用插座变量。这种方式会复杂一些,它需要提前进行一些配置工作,而这项工作很容易出错。不过这种方式也是更加常用的,特别在nib是自动加载的情况下更是如此。
插座变量连接属于一类连接,它有个名字,名字实际上是个字符串。当nib加载时,一些奇妙的事情就会发生。源对象与目标对象不再仅仅是nib中的潜在对象;它们会成为真实存在的实例。插座变量的名字用于定位插座变量源对象中相同名字的实例属性,而目标对象则会被赋给该属性。现在,源对象就会有一个指向目标对象的引用!
比如,假设nib中有个Dog对象和一个Person对象,Dog有个master实例属性。如果从nib中的Dog对象到Person对象创建一个插座变量,并且将其命名为"master",那么当nib加载时,Dog实例与Person实例就会创建出来,并且Person实例会被赋给该Dog实例的master属性(如图7-9所示)。
图7-9:插座变量是如何通过引用来指向nib所实例化的对象的
nib加载机制并不会神奇地创建出实例属性。也就是说,如果源对象不具有某个属性,那么当其实例化后,它并不会自动拥有这个属性。源对象所对应的类需要事先通过这个实例属性进行定义。这样,要想使用插座变量,我们需要在两处进行准备工作:一是源对象所对应的类,二是nib。这有点棘手,Xcode会帮助你,但也有可能把事情搞乱。(本章后面将会对此进行详细介绍。)
7.3.2 nib拥有者
要想让插座变量捕获到从nib中创建的实例引用,我们需要一个从nib外部的对象到nib内部的对象的一个插座变量。这看起来似乎是不可能的事情,但实际上是可以的。nib编辑器可以通过nib拥有者对象创建出这样的插座变量。首先,介绍如何在nib编辑器中找到nib拥有者对象;接下来介绍它到底是什么:
·在故事板场景中,nib拥有者是顶层的视图控制器。它是文档大纲中所列出场景中的第1个对象,也是场景停靠栏中所显示的第1个对象。
·在.xib文件中,nib拥有者是个代理对象。它是文档大纲或停靠栏中所显示的第1个对象,并且作为File's Owner列在Placeholders下面。
nib编辑器中的nib拥有者对象表示nib加载时nib之外已经存在的实例。当nib加载时,nib加载机制并不会实例化该对象;它已经是个实例了。实际上,nib加载机制会用真正的、已经存在的实例代替nib拥有者对象,使用它来实现涉及nib拥有者的任何连接。
不过请等等!nib加载机制是如何知道该用哪个真正的、已经存在的实例来代替nib中的nib拥有者对象呢?这是因为在nib加载时,系统会通过两种方式告知它:
·如果代码通过调用loadNibNamed:owner:options:或instantiateWith-Owner:options:来加载nib,那么你需要将拥有者对象作为owner:参数。
·如果视图控制器实例自动加载nib来获得主视图,那么视图控制器实例会将自身作为拥有者对象。
比如,回到Dog对象与Person对象。假设nib中有个Person nib对象,但没有Dog nib对象。Nib拥有者对象是个Dog。Dog有个master实例属性。我们配置一个从Dog nib拥有者对象到Person对象的插座变量,叫作"master"。接下来加载nib,将现有的Dog实例作为拥有者。nib加载机制会匹配Dog nib拥有者对象与这个已经存在的实际的Dog实例,并将新实例化的Person实例作为该Dog实例的master(如图7-10所示)。
回到Empty View,下面通过重新配置来说明这个机制。我们已经通过ViewController.swift的代码加载了View nib。代码会在ViewController实例中运行。因此,我们将该实例作为nib拥有者。这种配置有些乏味,不过请耐心一些,因为理解如何使用这种机制是非常重要的。下面就来看看具体步骤:
1.首先,ViewController需要一个实例属性。在ViewController类声明体的开头,插入属性声明,如下所示:
class ViewController: UIViewController { @IBOutlet var coolview : UIView!
图7-10:来自于nib拥有者对象的插座变量
你已经理解了var声明的含义;我们声明了一个名为coolview的实例属性。它声明为Optional,这是因为在ViewController实例创建时它才会拥有“真正的”值;在nib加载时它会持有该值。@IBOutlet属性告诉Xcode允许我们在nib编辑器中创建插座变量。
图7-11:创建插座变量
2.编辑View.xib。首先要确保nib拥有者对象作为一个ViewController实例。选中File's Owner代理对象并切换到身份查看器。在第一个文本框中(Custom Class下面),将Name值设为ViewController。在文本框外单击并保存。
3.现在创建插座变量!在文档大纲中,按住Control键并将File's Owner对象拖曳到View上;拖曳时有一根线会跟随着鼠标,松开鼠标。这时会出现一个提示,列出了可以创建的所有可能的插座变量(如图7-11所示)。其中有两个,分别是coolview与view。单击coolview(不是view!)。
4.最后,我们需要修改nib加载代码。现在不需要捕获实例化对象的顶层数组。现在要改变一下方式,我们会自己加载nib并将self作为拥有者。这样会自动设置coolview实例属性,因此我们就可以使用它了:
NSBundle.mainBundle.loadNibNamed("View", owner: self, options: nil)self.view.addSubview(self.coolview)
构建并运行,一切如预期一样!第1行会加载nib,并将coolview实例属性设为从nib实例化的视图。第2行会在界面上显示self.coolview,因为self.coolview现在就是该视图。
下面总结一下。预先的配置有些棘手,因为要在两个地方进行配置,即代码中和nib中:
·当nib加载时,如果一个类的实例是拥有者,那么这个类中一定会有一个实例属性(不仅要创建该属性,还要将其标记为@IBOutlet)。
·在nib编辑器中,当nib加载时,如果一个类的实例是拥有者,那么nib拥有者对象的类必须要设为这个类。
·在nib编辑器中,一定要创建插座变量,其名字与属性名相同,并且从nib拥有者到某个nib对象(只有当另外两项配置做好了才可以执行这个步骤)。
如果上面一切都做好了,那么当nib加载时,如果使用正确的类拥有者,该拥有者的实例属性就会被设为插座变量的目标。
Xcode 7中,当在nib中配置指向一个对象的插座变量时,文档大纲中所列出的对象名不再是泛泛的名字(如“View”),它会显示插座变量的名字(如“coolview”)。该名字只不过是个标签而已,它对于插座变量的操作没有任何影响,你可以在身份查看器中修改它。
7.3.3 自动配置nib
在某些情况下,拥有者类与nib的配置可以自动进行。既然已经了解了如何手工配置拥有者与nib,我们也可以理解这些自动化配置。
一个重要的示例是视图控制器是如何获取其主视图的。视图控制器有一个view属性。实际的视图通常来自于nib。这样,当nib加载时,视图控制器就需要充当拥有者的角色,还需要有一个从nib拥有者对象到该视图的view插座变量。如果查看持有视图控制器主视图的实际nib,你就会发现这一点。
回到Empty Window项目。编辑Main.storyboard。它有一个场景,其nib拥有者对象是View Controller对象。在文档大纲中选中View Controller,切换至身份查看器。它会显示出nib拥有者对象的类实际上就是ViewController!
保持文档大纲中View Controller为选中状态,切换至连接查看器。它显示出实际上有一个从View Controller到View对象的插座变量连接,这个插座变量叫作"view"!如果将鼠标悬浮在该插座变量连接上,那么画布中的View对象就会高亮显示,帮助你进行识别。
这说明了视图控制器是如何获取到其主视图的!当视图控制器需要其主视图时(因为视图要显示在界面上),view nib就会加载——视图控制器会成为拥有者。这样,视图控制器的view属性会被设为这里所设计的视图。接下来,视图会显示在界面上:它与上面的内容会出现在运行的应用上。
对于第6章的Truly Empty项目亦如此。编辑MyViewController.xib。nib拥有者对象是File's Owner代理对象。选中File's Owner对象,切换至身份查看器。它会显示出nib拥有者对象的类实际上是MyViewController!切换至连接查看器,它会显示出有一个连接到View对象的插座变量,名为"view"!
这说明了视图控制器是如何获得其主视图的。在调用MyViewController(nibName:"MyViewController",bundle:nil)实例化视图控制器时,我们会告诉它去哪里寻找其nib。不过,nib本身已经正确配置了,因为在创建MyViewController类并勾选上“Also create XIB file”复选框时,Xcode会帮我们做这件事。视图控制器加载nib时会将自身作为拥有者,插座变量即可生效:来自于nib文件的视图会成为视图控制器的view,并显示在界面上。
7.3.4 误配置的插座变量
创建插座变量并能正常使用涉及几件事情。我敢保证你今后肯定会在这个地方栽跟头,插座变量无法正常使用。别生气,也别担心;请准备好!每个人都会遇到这个事情。重要的是,要能识别出问题所在,这样才能知道到底哪里出错了。接下来我们有意做错一些事情,目的是看看哪些情况会导致插座变量配置不正确:
插座变量名与源类中的属性名不匹配
从Empty Window示例开始。运行项目来证明一切都如我们所愿。现在,在ViewController.swift中,将属性名改为badview:
@IBOutlet var badview : UIView!
为了让代码编译通过,你还需要在viewDidLoad中修改对该属性的引用:
self.view.addSubview(self.badview)
代码编译通过。不过当运行时,应用会崩溃,控制台上转出的消息是:“This class is not key value coding-compliant for the key coolview”。
从技术上来说,这条消息表示当nib加载时,nib中的插座变量名(依旧是coolview)与nib拥有者的属性名不匹配,这是因为我们将该属性名修改成了badview,这导致配置出现了问题。实际上,一切都是正确的,不过我们绕过了nib编辑器,从插座变量源的类中删除了对应的实例属性。当nib加载时,运行时无法匹配插座变量的名字与插座变量源(ViewController实例)中的任何属性,应用因此崩溃。
还有一些情况也会导致这种误配置。比如,你可以修改一些东西,导致nib拥有者成为错误类的实例:
NSBundle.mainBundle.loadNibNamed("View", owner: NSObject, options: nil)
我们将owner设为一个通用的NSObject实例。结果一样:NSObject类没有与插座变量名相同的属性,这样当nib加载时应用就会崩溃,提示拥有者并不是“键值编码兼容的”。会导致同样错误的另一个常见做法是在nib中将nib拥有者类设为错误的类。
nib中没有插座变量
在ViewController.swift中,将之前示例对属性名的引用由badview改回到coolview。运行项目来证明修改是正确的。现在来做一些破坏!编辑View.xib。选中File's Owner并切换至连接查看器,单击第2个椭圆形左侧的X来取消coolview插座变量的连接。运行项目,应用会崩溃,控制台上转出的消息是:“Fatal error:unexpectedly found nil while unwrapping an Optional value”。
将插座变量从nib中删除。当nib加载时,ViewController实例属性coolview(其类型是个隐式、展开的Optional,它包装了一个UIView,即UIView!)不会被设置。这样,它会保持其初始值,即nil。接下来,将其放到界面中来使用隐式展开的Optional:
self.view.addSubview(self.coolview)
Swift会展开Optional,不过你无法展开nil,因此程序会崩溃。
没有视图插座变量
对于这种情况来说,你需要使用第6章的Truly Empty示例,该示例会从一个.xib文件中加载视图控制器的主视图;我无法使用.storyboard文件来说明问题,因为故事板编辑器不允许这么做。在Truly Empty项目中,编辑MyViewController.xib文件。选中File's Owner对象并切换至连接查看器,取消view插座变量的连接。运行项目。程序启动时会崩溃,控制台转出的消息是:“Loaded the‘MyViewController’nib but the viewoutlet was not set”。
控制台消息已经说明了情况。作为视图控制器主视图源的nib必须要有一个从视图控制器(nib拥有者对象)到视图的view插座变量。
7.3.5 删除插座变量
一致性地删除插座变量(也就是说,不会导致上面提及的那些问题)涉及要同时修改几处地方,就像创建插座变量一样。建议按照下面这个顺序进行:
1.取消nib中插座变量的连接。
2.从代码中删除插座变量声明。
3.尝试编译,让编译器捕获其余的问题。
比如,假设要从Empty Window项目中删除coolview插座变量,遵循上面提到的3个步骤,做法如下所示:
1.取消nib中插座变量的连接。要想做到这一点,请编辑View.xib,选中源对象(File's Owner代理对象),然后在连接查看器中单击X取消coolview插座变量的连接。
2.从代码中删除插座变量声明。要想做到这一点,请编辑ViewController.swift,删除或注释掉@IBOutlet声明这一行。
3.删除对属性的其他引用。最简单的方式是构建项目;编译器会对ViewController.swift中self.coolview这一行代码报错,因为现在已经没有v属性了。删除或注释掉该行,再次构建,验证一切正常。
7.3.6 创建插座变量的其他方式
之前创建插座变量的方式是这样的:首先在类文件中声明一个实例属性,然后在nib编辑器中,按住Control键,从文档大纲的源(该类的实例)拖曳到目标处,从弹出列表中选择所需的插座变量属性。Xcode提供了多种方式来创建插座变量,这里就不再一一列举了。我会介绍一些常见的方式。
继续使用Empty Window项目与View.xib文件。请记住,所有这些与对.storyboard文件的操作是一样的。
通过连接查看器删除View.xib中的插座变量(如果之前没有做过)。在ViewController.swift中,创建(或取消注释)属性声明,然后保存:
@IBOutlet var coolview : UIView!
现在来试一下!
从源连接查看器中拖曳
可以拖曳nib编辑器的连接查看器中的圆圈来连接插座变量。在View.xib中,选中File's Owner并切换至连接查看器。coolview插座变量会列出来,不过它尚未连接:右侧的圆圈是打开的。从coolview旁边的圆圈拖曳到nib中的UIView对象上。可以拖曳到画布或文档大纲中的视图上。在拖曳圆圈时不需要按住Control键,也没有提示列表,因为你是从特定的插座变量上拖曳的,Xcode知道是哪个。
从目标连接查看器中拖曳
现在按照相反的方向完成相同的步骤。删除nib中的插座变量。选中View并打开连接查看器。我们需要一个将该视图作为目标的插座变量:这是个“引用插座变量”。从New Referencing Outlet旁边的圆圈拖曳到File's Owner对象上。提示列表会出现:单击coolview来创建插座变量连接。
从源提示列表拖曳
可以使用与连接查看器相同的提示列表。下面就从这个提示列表开始。再一次,删除连接查看器中的插座变量。按住Control键并单击File's Owner。这时会弹出一个提示列表,看起来像是连接查看器!从coolview右侧的圆圈拖曳到UIView上。
从目标提示列表拖曳
再一次,我们按照相反的方向完成相同的步骤。删除连接查看器中的插座变量。在画布或文档大纲中,按住Control键并单击视图。这时会出现一个提示列表,显示其连接查看器。从New Referencing Outlet旁边的圆圈拖曳到File's Owner上。这时又会出现一个提示列表,列出了可能的插座变量;单击coolview。
再一次,删除插座变量。现在通过在代码与nib编辑器间拖曳来创建插座变量。这要求你同时在两处进行操作:你需要一个辅助窗格。在主编辑器窗格中,打开ViewController.swift。在辅助窗格中,打开View.xib,这样视图就是可编辑的了。
从属性声明拖曳到nib
代码中,属性声明旁边有个空心圆圈。你觉得它是干什么的呢?将其拖曳到nib编辑器的View上(如图7-12所示)。这时,nib中会形成插座变量连接;可以通过连接查看器看到这一点,回到代码,你会发现圆圈不再是空心的了。将鼠标悬浮在填充后的圆圈上或单击它,看看它连接到了nib中的哪个插座变量上。单击这个实心圆圈会弹出一个菜单,可以单击菜单转向目标对象。
图7-12:从代码拖曳到nib编辑器来连接插座变量
还有一种方式,也是最令人惊讶的方式。保留上一示例的两个窗格排列。再一次,删除插座变量(你可能需要使用连接查看器或nib编辑器中的弹出列表)。再从代码中删除@IBOutlet这一行!我们来创建属性声明并连接插座变量,一步即可搞定!
从nib拖曳到代码
按住Control键将nib编辑器中的视图拖曳到类ViewController的声明体中。提示列表会显示出Insert Outlet或Outlet Collection(如图7-13所示)。松开鼠标。这时会出现一个弹出层,可以配置插入代码中的声明。按照图7-14进行配置:你需要一个插座变量,该属性应该命名为coolview。单击Connect。这时,属性声明会插入代码中,插座变量会在nib中进行连接,这一切只需一步操作即可。
直接连接代码与nib编辑器来创建插座变量确实非常酷,也非常方便,不过要当心:并不存在这种直接的连接。要想让插座变量能够正常使用,总是要有两部分内容:类中的实例属性以及nib中的插座变量,其名字是相同的,来自于该类的实例。正是名字与类的同一性才使得nib加载时它们能在运行期匹配上。Xcode会帮助你将一切准备好,不过实际上并不是什么魔法将代码连接到nib上。
图7-13:从nib编辑器拖曳到代码来创建插座变量
图7-14:配置属性声明
7.3.7 插座变量集合
插座变量集合指的是与相同类型对象的多个连接匹配(nib中)的数组实例属性(代码中)。
比如,假设一个类包含了如下属性声明:
@IBOutlet var coolviews: [UIView]!
结果就是在nib编辑器中,如果选中了该类的实例,那么连接查看器就会在Outlet Collections而非Outlets下面列出coolviews。这意味着你可以构建多个coolviews插座变量,每个都会连接到nib中的不同UIView对象上。当nib加载时,这些UIView实例会成为数组coolviews的元素;插座变量构建的顺序就是数组中元素的排列顺序。
这么做的好处在于代码可以通过数字(数组索引)来引用从nib实例化的多个界面对象,而不必为每个对象使用不同的名字。在构建如自动布局约束与手势识别的插座变量时,这么做是非常有用的。
7.3.8 动作连接
就像插座变量连接一样,动作连接也是让nib中的一个对象能够引用另外一个对象的方式。不过,它并非属性引用,而是消息发送引用。
所谓动作,就是当用户对Cocoa UIControl界面对象(一个控件)执行了某种操作后由其产生并发送给其他对象的消息,如轻拍控件等。会导致控件发出动作消息的各种用户行为叫作事件。要想查看完整的事件列表,请查看UIControl类的文档,位于“Control Events”下。比如,对于UIButton,用户轻拍按钮对应于UIControlEvents.TouchUpInside事件。
控件对象要知道下面3点才能让这个架构正常运作:
·响应什么控件事件。
·当该控件事件(动作)发生时会发送什么消息(调用的方法)。
·将消息发送给哪个对象(目标)。
nib中的动作连接会将这3点融入自身当中。它拥有作为源的控件对象;其终点就是目标;在构建时,你会指明动作连接,以及哪个控件事件与动作消息。为了构建动作连接,首先需要配置目标对象所对应的类,使之拥有适合于作为动作消息的方法。
为了尝试动作连接,nib中需要有一个UIControl对象,如按钮。Empty Window项目的Main.storyboard文件中可能已经有了这样的按钮。不过,当应用运行时,从View.xib中所加载的视图可能会覆盖这个按钮。因此,首先需要在ViewController.swift中清除ViewController类声明,使之不再有插座变量属性与手工加载nib代码;如下就是清理之后的代码:
class ViewController: UIViewController {}
下面将Empty Window项目中的视图控制器作为按钮UIControlEvents.TouchUpInside事件(表示轻拍了按钮)所发出的动作消息的目标。我们需要在视图控制器中添加一个方法,当轻拍按钮后会调用该方法。为了看起来明显一些,我们让视图控制器弹出一个警告窗口。将如下方法添加到ViewController.swift声明体中:
class ViewController: UIViewController { @IBAction func buttonPressed(sender:AnyObject) { let alert = UIAlertController( title: "Howdy!", message: "You tapped me!", preferredStyle: .Alert) alert.addAction( UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) }}
@IBAction属性就像是@IBOutlet一样:它用来提示Xcode,让Xcode在nib编辑器中加入这个方法。实际上,如果查看nib编辑器,你会发现它就在那儿:编辑Main.storyboard,选中View Controller对象并切换至连接查看器,你会看到buttonPressed:现在位于Received Actions下面。
在Main.storyboard中的唯一一个场景中,顶层View Controller的View应该会包含一个按钮(本章之前创建的,如图7-5所示)。如果不存在,请添加一个,然后将其放到视图左上角。我们的目标是将按钮的Touch Up Inside事件(作为动作)连接到ViewController中的buttonPressed:方法上。
与插座变量连接一样,动作连接也有一个源和一个目标。这里的源就是按钮,目标是View Controller,View Controller实例作为包含按钮的nib的拥有者。有多种方式可以构建这个插座变量连接,这与动作连接都是完全对应的。区别在于必须要配置连接的两端。在按钮(源)端,需要将Touch Up Inside指定为所要使用的控件事件;幸好,这是UIButton的默认值,因此可以省略这一步。在视图控制器(目标)端,需要将buttonPressed:指定为要调用的动作方法。
下面按住Control键从按钮拖曳到nib编辑器中的视图控制器来构建动作连接:
1.按住Control键从按钮(在画布或文档大纲中)拖曳到文档大纲中所列出的View Controller(或拖曳到画布中视图上面的场景停靠栏中的视图控制器图标上)来创建连接。
2.这时会出现一个提示列表(如图7-15所示),列出了可能的连接;它会列出Segue,以及Sent Event,特别是buttonPressed:。
3.单击提示列表中的buttonPressed:。
现在会形成动作连接。这意味着当应用运行时,只要按钮的Touch Up Inside事件触发(表示按钮被按下),那么它就会向目标(视图控制器实例)发送消息buttonPressed:。我们知道这个方法应该做什么事情:它会弹出一个警告窗口。试一下吧!构建并运行应用,当应用出现在模拟器中时,单击按钮,事情与你想的一样!
7.3.9 创建动作的其他方式
如果在ViewController.swift中创建了动作方法,那么还有下面几种方式可以在nib中创建动作连接:
图7-15:展示出动作方法的提示列表
·按住Control键并单击视图控制器,这会弹出一个提示列表,类似于连接查看器。从buttonPressed:(位于Received Actions下)拖曳到按钮。这会弹出另一个提示列表,列出可能的控件事件,单击其中的Touch Up Inside。
·选中按钮并使用连接查看器。从Touch Up Inside的圆圈拖曳到视图控制器。这时会弹出一个提示列表,列出视图控制器中已知的动作方法;单击buttonPressed:。
·按住Control键并单击按钮。这时会弹出一个提示列表,类似于连接查看器。像之前一样操作。
·在一个窗格中显示ViewController.swift,在另一个窗格中显示故事板。ViewController.swift中的buttonPressed:方法左侧有一个圆圈。从该圆圈拖曳到nib中的按钮上。
与插座变量连接一样,创建动作连接最为直观的方式就是从nib编辑器拖曳到代码中,插入动作方法并在nib中构建动作连接,一步即可完成。首先请删除代码中的buttonPressed:方法以及nib中的动作连接。在一个窗格中显示ViewController.swift,在另一个窗格中显示故事板。现在:
1.按住Control键,从nib编辑器中的按钮拖曳到ViewController类声明体的空白区域。代码中会弹出一个提示列表,提示创建插座变量或动作,松开鼠标。
2.一个弹出框会出现,这一块比较棘手。在默认情况下,该弹出框用于创建插座变量连接,但这并非你想要的;你需要的是动作连接!将连接弹出菜单修改为Action。现在输入动作方法的名字(buttonPressed)并配置声明的其他部分(默认值就可以了,如图7-16所示)。
Xcode会在nib中构建动作连接,并向代码中插入一个桩方法:
@IBAction func buttonPressed(sender: AnyObject) {}
图7-16:配置动作方法声明
这仅仅是个桩方法(Xcode肯定不知道这个方法要做什么),在实际情况下,你需要在花括号之间插入一些功能代码。就像插座变量连接一样,动作方法代码旁边的实心圆圈表示Xcode已经认为连接就绪,可以单击这个实心圆圈查看、导航到连接中的源对象。
7.3.10 误配置的动作
与插座变量连接一样,配置动作连接涉及两端(nib与代码)的一些处理与配置,这样它们才能匹配上。因此,可以有意破坏动作连接的配置,并让应用崩溃。通常的误配置出现在嵌入nib动作连接中的动作方法名与代码中的动作方法名不匹配。
为了证明这一点,请将代码中的函数名由buttonPressed修改为其他名字,如buttonPushed。运行应用并轻拍按钮。应用会崩溃并在控制台中显示错误消息:“Unrecognized selector sent to instance”。选择器是一条消息,即方法的名字。运行时尝试向对象发送一条消息,不过该对象并没有与之对应的方法(因为方法已经重命名了)。如果看一下错误消息开头,你会发现它甚至告诉你了这个方法的名字:
-[Empty_Window.ViewController buttonPressed:]
运行时表示它尝试调用Empty Window模块ViewController类中的buttonPressed:方法,但ViewController类却没有这个方法。
7.3.11 nib之间的连接——不行!
不能在一个nib中的对象与另一个nib中的对象之间创建插座变量连接与动作连接。比如:
·不能在两个不同的.xib文件中打开nib编辑器,然后按住Control键从一个文件拖曳到另一个文件来创建连接。
·在.storyboard文件中,不能按住Control键从一个场景中的对象拖曳到另一个场景中的对象来创建连接。
如果想这么做,那就说明你还没有理解nib(或场景、连接)到底是什么。
原因很简单:nib中的对象会在nib加载时成为实例,因此在nib中将其连接起来是合理的,因为我们知道当nib加载时会有哪些实例。两个对象可以从nib中实例化,其中一个可能是代理对象(nib拥有者),不过它们必须位于同一个nib中,这样当nib加载时,我们可以根据具体情况来配置实际实例之间的关系。
如果插座变量连接或动作连接是从一个nib中的对象到另一个nib中的对象建立的,那么就没有办法知道真正连接的实例是什么,因为它们是不同的nib,会在不同时刻加载。从一个nib生成的实例与从另一个nib生成的实例之间的通信问题是程序中实例之间通信方式的一种特殊情况,详见第13章。