首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》11.4 委托

关灯直达底部

委托是一种面向对象的设计模式,指的是两个对象间的关系,其中主对象的行为是通过另一个对象定制或协助处理的。第2个对象就是主对象的委托。这里面不涉及子类化,实际上,第1个对象对委托类一无所知。

由于Cocoa实现了委托,下面来看看委托的运作方式。内建的Cocoa类有一个通常叫作delegate的实例变量(名字中当然会有delegate了)。对于Cocoa类的某些实例来说,你会将该实例变量的值设为你自己的类的实例。在运行中的某些时刻,Cocoa类会通过向其委托发送消息来决定接下来该做什么:如果Cocoa类实例发现其委托不为nil,同时该委托可以接收这个消息,那么Cocoa实例就会向其委托发送消息。

回忆一下第10章关于协议的介绍,委托大量使用了协议。过去,委托方法列在了Cocoa类的文档中,其方法签名通过非正式协议(NSObject的一个类别)来告知编译器。但现在,类的委托方法通常会列在协议自己的文档中。目前有70多个Cocoa委托协议,这表明Cocoa在大量使用委托。大多数委托方法都是可选的,但有时你会发现有些则是必需的。

11.4.1  Cocoa委托

要想通过委托定制Cocoa实例的行为,你需要从一个类开始,这个类需要实现相关的委托协议。当应用运行时,你会将Cocoa实例的delegate属性(或其他名字)设为你的类一个实例。你可以通过代码完成,也可以在nib中完成,方式是将实例的delegate插座变量(或其他名字)连接到作为委托的恰当的实例上。除了作为该实例的委托,委托类还可能会做其他一些事情。事实上,委托的一个好处就在于你可以随意在类架构中插入委托代码;委托类型是个协议,因此实际的委托可以是任何类的实例。

在这个简单的示例中,我要确保应用的根视图控制器(一个UINavigationController)不允许应用旋转,当该视图控制器起作用时,应用界面只能位于纵向。不过UINavigationController并不是我定义的类;它是Cocoa定义的。我自己的类是个不同的视图控制器,即UIViewController子类,它作为UINavigationController的孩子。那么这个孩子如何告诉父亲该如何旋转呢?UINavigationController有个类型为UINavigationControllerDelegate(这是个协议)的delegate属性。在需要知道该如何旋转时,它会向这个委托发送navigationControllerSupportedInterfaceOrientations消息。因此,为了能够对非常早期的生命周期事件作出响应,我的视图控制器会将其自身设为UINavigationController的委托。它还实现了navigationControllerSupportedInterfaceOrientations方法。问题很快就迎刃而解了:


class ViewController : UIViewController, UINavigationControllerDelegate {    override func viewDidLoad {        super.viewDidLoad        self.navigationController?.delegate = self    }    func navigationControllerSupportedInterfaceOrientations(        nav: UINavigationController) -> UIInterfaceOrientationMask {            return .Portrait    }}  

Apple的共享应用实例UIApplication.sharedApplication()有一个委托,它在应用的生命周期中扮演着重要的角色,甚至连Xcode应用模版都会自动提供一个,即名为AppDelegate的类。第6章介绍过如何通过调用UIApplicationMain来启动应用,它会实例化AppDelegate类,并让该实例成为共享应用实例(已经创建好了)的委托。正如第10章所指出的那样,AppDelegate正式使用了UIApplicationDelegate协议,这表示它已经为该角色做好了准备;接下来会向应用委托发送respondsToSelector:,看看实现了哪些UIApplicationDelegate协议方法。然后会向应用委托实例发送消息,让其知晓应用生命周期中的主要事件。这正是UIApplicationDelegate协议方法UIApplicationDelegateOptions:如此重要的原因所在;它是你的代码可以运行的最早期阶段之一。

UIApplication委托方法也用作通知。这样,除了应用委托,其他实例也能很便捷地接收到应用生命周期事件(通过注册)。还有其他一些类提供了类似的重复事件;比如,UITableView的tableView:didSelectRowAtIndexPath:委托方法就是通过通知UITableViewSelectionDidChangeNotification进行匹配的。

根据约定,很多Cocoa委托方法名都会包含情态动词should、will或did。will消息会在某件事发生前发送给委托;did消息会在某件事刚刚发生后发送给委托。should消息比较特殊:它返回一个Bool,如果为true就做出响应;如果为false就不会。文档会列出其默认响应是什么;如果默认响应可以接受,那就无须再实现should方法了。

在很多情况下,属性会控制某种总体性行为,而我们可以通过委托方法在运行期根据情况来修改该行为。比如,用户是否可以轻拍状态栏让滚动视图快速滚动到顶部是由滚动视图的scrollsToTop属性决定的;不过,即便该属性值为true,你也可以通过让滚动视图委托的scrollViewShouldScrollToTop:返回false来针对某种特定的轻拍动作而禁止该行为。

在搜索文档以查找如何收到某种事件的通知时,请确保查看相对应的委托协议(如果有)。你可能想要知道用户什么时候轻拍了UITextField开始进行编辑了?这是无法在UITextField类文档中找到的;你需要查看的是UITextFieldDelegate协议的textFieldDidBeginEditing:,诸如此类。

11.4.2 实现委托

对于Cocoa中的委托来说,其职责是由协议来描述的,这种模式值得你在编写代码时效仿。你需要通过实践来掌握这种模式,并且要多花时间,不过它通常都是正确的解决方案,因为它会恰当地将知识与职责分配给相关的各种对象。

我们来考虑一个实际的情况。在我开发的一个应用中有一个视图控制器,其视图包含了3个滑块,用户可以移动滑块来选择颜色。此外,该视图控制器是UIViewController的子类,名字是ColorPickerController。当用户轻拍Done或Cancel时,视图会隐藏起来;不过首先,用于展现该视图的代码需要知道用户选择了哪个颜色。因此,我需要从ColorPickerController实例向展现该视图的实例发送一条消息。

下面是个消息声明,ColorPickerController在销毁前会发送这条消息:


func colorPicker (picker:ColorPickerController,    didSetColorNamed theName:String?,    toColor theColor:UIColor?)  

问题在于:应该在哪里以及如何声明这个方法呢?

现在,我知道应用中实际用于呈现ColorPickerController:的实例所对应的类,那就是SettingsController。因此,我可以在SettingsController中声明这个方法。不过,如果这么做,那就意味着为了向SettingsController发送这条消息,ColorPickerController必须得知道用于呈现它的视图是SettingsController。但让SettingsController接收消息仅仅是个特例而已;它应该对用于呈现与隐藏ColorPickerController的所有类开放,这样才能接收到这条消息。

因此,我们希望ColorPickerController本身来声明自己会调用的方法;它可以向某个接收者随意发送消息,无论该接收者所对应的类是什么都如此。这正是协议的用武之地!解决方案就是让ColorPickerController定义一个协议,并且将该方法作为协议的一部分;让呈现ColorPickerController的类遵循该协议。ColorPickerController还有一个类型适当的delegate属性;这提供了通信的通道,并且告诉编译器发送这条消息是合法的:


protocol ColorPickerDelegate : class {    // color == nil on cancel    func colorPicker (picker:ColorPickerController,        didSetColorNamed theName:String?,        toColor theColor:UIColor?)    }    class ColorPickerController : UIViewController {        weak var delegate: ColorPickerDelegate?    // ...}  

(请参见第5章了解这里所用的weak特性的含义与原因。)当SettingsController实例创建并配置好了ColorPickerController实例后,它还会将自身设为ColorPickerController的delegate——它是可以这么做的,因为它使用了协议:


extension SettingsController : ColorPickerDelegate {    func showColorPicker {        let colorName = // ...        let c = // ...        let cpc = ColorPickerController(colorName:colorName, andColor:c)        cpc.delegate = self        self.presentViewController(cpc, animated: true, completion: nil)    }    func colorPicker (picker:ColorPickerController,        didSetColorNamed theName:String?,        toColor theColor:UIColor?) {            // ...    }}  

现在,当用户选择颜色时,ColorPickerController就知道该向谁发送colorPicker:didSetColorNamed:toColor:了,就是其委托!编译器也允许这么做,因为委托使用了ColorPickerDelegate协议:


@IBAction func dismissColorPicker(sender : AnyObject?) { // user tapped Done    let c : UIColor? = self.color    self.delegate?.colorPicker(        self, didSetColorNamed: self.colorName, toColor: c)}