委托是一种对象,当向外委托任务的对象遇到程序中的事件时,它的委托可以代表它对事件进行处理,或者和它进行协调。向外委托任务的对象通常是一个响应者对象—即继承自NSResponder
的对象—负责响应用户事件。委托则是受托进行事件的用户界面控制,或者至少根据应用程序的具体需要对事件进行解释的对象。
为了更好地理解委托的价值,让我们考虑一个复活的Cocoa对象,比如一个窗口(NSWindow
的实例)或者表视图(NSTableView
的实例)。这些对象的设计目的是以一般的方式实现一个具体的角色;举例来说,窗口对象负责响应窗口控件的鼠标操作,处理象关闭窗口、调整尺寸、以及移动窗口的位置这样的事件;这个受限而又具有一般性的行为必然限制该对象认识一个事件对应用程序其它地方的影响,特别是当被影响的行为只存在于您的应用程序的时候。委托为您的定制对象提供一种方法,使它可以就应用程序特有的行为和复活对象进行通讯。
委托的编程机制使对象有机会对自己的外观和状态、以及程序在其它地方发生的变化进行协调,这些变化通常是由用户动作触发的。更重要的是,委托使一个对象有可能在没有进行继承的情况下改变另一个对象的行为。委托几乎总是您的一个定制对象,它通过定义将应用程序具体逻辑结合到程序中,而这些逻辑是具有一般性的,是向外委托任务的对象自身不可能知道的。
本部分包括如下主要内容:
委托是如何工作的
委托机制的设计是很简单的(图5-2)。希望向外委托任务的类需要有一个插座变量,通常命名为delegate
,并包含对该插座变量进行设置和访问的方法。它还需要声明一或多个方法,构成一个非正式的协议,但不进行实现。非正式协议通常是希望向外委托任务的类的一个范畴,和正式协议的不同之处在于,它不需要实现协议中的所有方法。在非正式协议中,委托只实现希望进行协调或对缺省行为实施影响的方法。
图5-2 委托的机制
非正式协议的方法标记着进行任务委托的对象需要处理或预期发生的重大事件。该对象希望就这些事件和委托进行交流,或者就即将发生的事件向委托请求输入或批准。举例来说,当用户点击一个窗口的关闭按键时,窗口对象会向委托发送windowShouldClose:
消息;这就使委托有机会否决或推迟窗口的关闭,如果必须保存窗口关联数据的话(参见图5-3)。
图5-3 一个更接近现实的、涉及委托的序列
向外委托任务的对象只将消息发送给实现了相应方法的委托。在发送消息之前,它会首先对委托调用NSObject
的respondsToSelector:
方法,确认委托是否实现该方法。这种预先检查是非正式协议设计的关键。
委托消息的形式
委托方法有个命名约定,即以进行委托的Application Kit对象的名字作为开头—如应用程序、窗口、控件等,名字是小写的,且没有“NS”前缀;这个对象名后面通常(但并不总是)紧接着一个辅助的动词,指示被报告的事件在时间上的状态,换句话说,这个动词指示事件是即将发生(“Should”或者“Will”),还是刚刚发生(“Did”或者“Has”)。这个时间上的区别可以帮助我们区分那些希望得到一个返回值和不需要返回值的消息。程序清单5-1列出了期望得到返回值的Application Kit委托方法。
程序清单5-1 带有返回值的委托方法示例
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename; |
- (BOOL)textShouldBeginEditing:(NSText *)textObject; |
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; |
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame; |
实现这些方法的委托可以阻塞(前面的两个方法可以通过返回NO
来实现)、延迟 (通过在applicationShouldTerminate:
方法中返回NSTerminateLater
)即将发生的事件,或者改变Cocoa建议的参数(比如最后一个方法中的外形方框)。
另外一类委托方法是由不期望返回值的消息调用的,因此返回类型为void
。这些消息纯粹是信息性的,且方法的名称通常包含“Did” 或其它指示事件已经发生的词。程序清单5-2列出了一些这类委托方法的实例。
程序清单5-2 返回void的委托方法示例
- (void) tableView:(NSTableView*)tableView mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn; |
- (void)applicationDidUnhide:(NSNotification *)notification; |
- (void)applicationWillBecomeActive:(NSNotification *)notification; |
- (void)windowDidMove:(NSNotification *)notification; |
有一些和最后一组消息有关的事项需要注意。第一是辅助动词“Will” (如第三个方法)并不一定意味着需要返回值。在这种情况下,事件即将发生且不能被阻塞,而这个消息使委托有机会为事件做好准备。
另一个有意思的点是关于最后三个方法的。这三个方法都只有一个参数,即一个NSNotification
对象,意味着调用这些方法是特定的通告发出的结果。举例来说,windowDidMove:
方法和NSWindow
的NSWindowDidMoveNotification
方法是相互关联的。部分对此进行详细的讨论。但是在这里,理解通告和委托消息的关系是很重要的。向外委托的对象自动使自己的委托成为自己发出的所有通告的观察者。委托需要做的是实现相关联的方法,以获取通告。
为了使您的定制类成为Application Kit对象的委托,只要简单地在Interface Builder中将实例和delegate
插座变量相连接就可以了。或者,也可以在程序中调用向外委托的对象的setDelegate:
方法来进行设置,这样的设置最好早做,比如在awakeFromNib
方法中进行。
委托和Application Kit
应用程序中向外委托的对象通常是一个NSApplication
、NSWindow
、或者NSView
对象。委托在典型情况下是个对象,但也不是一定如此。它通常是一个定制对象,负责控制应用程序的一部分(也就是说,是协调控制器对象)。表5-1列出了定义委托的Application Kit类。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
向外委托的对象并不(且不应该)保持自己的委托。但是,它的客户对象(通常是应用程序)需要负责保证它们的委托可以接收委托消息。为此,它们可能需要对委托进行保持。这个警示同样适用于数据源、通告的观察者,以及动作消息的目标。
某些Application Kit类有一种更为严格的委托,称为模式委托。这些类的对象(比如NSOpenPanel
)会弹出模式对话框,当用户点击对话框的OK按键时,指定委托中的处理函数就会被调用。模式委托的应用限制在模式对话框操作的范围内。
委托的存在有一些其它的编程用法。举例来说,通过委托,一个程序中的两个协调控制器可以很容易地找到彼此,并进行通讯。比如,控制整个应用程序的对象可以通过类似如下的代码找到应用程序的查看器窗口(假定它时当前的关键窗口):
id winController = [[NSApp keyWindow] delegate]; |
您的代码也能通过执行下面的代码找到应用程序控制器对象—它定义为全局应用程序实例的委托:
id appController = [NSApp delegate]; |
数据源
数据源很像委托,区别在于委托处理的是用户界面的控制,而数据源处理的是数据的控制。 数据源是由NSView
对象保有的插座变量,举例来说,表视图和大纲视图都需要一个提供可视数据的源。视图的数据源和委托通常是同一个对象,但也可以是其它对象。和委托对象一样,数据源必须实现某个非正式协议中的一个或多个方法,以便为视图提供其所需要的数据,在更高级的实现中,它还可以处理用户在视图中直接编辑的数据。
和委托一样,数据源对象必须可以接收由请求数据的对象发出的消息。使用数据源对象的应用程序必须确保该对象的持久性,在必要的时候对其进行保持操作。
数据源负责保证它们分发给用户界面对象的数据对象的持久性。换句话说,它们负责那些对象的内存管理。然而,每当视图对象(比如大纲视图或表视图)对数据源的数据进行访问时,会在需要使用该数据的时候对其进行保持。但是它们并不使用很长时间,通常只是足以对该数据进行显示就可以了。
实现一个定制类的委托
通过下面的步骤可以为您的定制类实现一个委托:
-
在您的类头文件中声明一个委托存取方法:
- (id)delegate;
- (void)setDelegate:(id)newDelegate;
-
实现该存取方法。请注意,在setter方法中,您应该仅拥有委托的弱引用,避免循环保持(也就是说,不要对委托进行保持或拷贝):
- (id)delegate {
return delegate;
}
- (void)setDelegate:(id)newDelegate {
delegate = newDelegate;
}
-
声明一个包含委托编程接口的非正式协议。非正式协议是
NSObject
类的范畴。@interface NSObject (MyObjectDelegateMethod)
- (BOOL)operationShouldProceed;
@end
请参见,该部分给出了对您自己的委托方法进行命名的建议。
-
在调用一个委托方法时,请向委托发送
respondsToSelector:
消息,确认其是否实现该方法。- (void)someMethod {
if ( [delegate respondsToSelector:@selector(operationShouldProceed)] ) {
if ( [delegate operationShouldProceed] ) {
// do something appropriate
}
}
}