第23条:通过委托与数据源协议进行对象间通信
- 此模式可将数据与业务逻辑解耦。例如,用户界面有一个UITableView,那么此视图只应包含显示数据所需的逻辑代码,而不应决定要显示何种数据以及数据之间如何交互等问题。
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- 当某个委托方法需要被调用多次的时候,可以使用含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。
@protocol EOCNetworkFetcherDelegate
@optional
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didUpdateProgressTo:(float)progress;
@end
譬如第三个方法,在网络数据获取的生命期(life cycle)里会多次调用,如果每次都检查委托对象是否能响应此选择子,那就显得多余了。
在写例子的时候碰到一个问题,书上的例子是这样的
@protocol EOCNetworkFetcherDelegate
@optional
-(void)networkFetcher:(EOCNetworkFetcher *)fetcherdidReceiveData:(NSData*)data;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcherdidFailWithError:(NSError*)error;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcherdidUpdateProgressTo:(float)progress;
@end
@property(nonatomic,weak) id<EOCNetworkFetcherDelegate> delegate;
-(void)setDelegate:(id<EOCNetworkFetcher>)delegate
{
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didFailWithError= [delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}
可是属性命名是
id<EOCNetworkFetcherDelegate>
等到set的时候咋变成
id<EOCNetworkFetcher>
了呢?
并且如果用前者,根本就没有respondsToSelector这个方法。
因为自从发现block后,我就基本只使用block了,然后某天面试,当我在大肆表扬block有多么多么好用的时候,他说,你知道吗,其实苹果官方比较推崇使用delegate。额……
所以碰到这个问题,只能翻回以前的代码看看我以前是怎么做的。
原因竟然在于定义协议的时候没有加<NSObject>。所以以上代码应该为。
@protocol EOCNetworkFetcherDelegate
@optional
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData*)data;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError*)error;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didUpdateProgressTo:(float)progress;
@end
@property (nonatomic,weak) id delegate;
-(void)setDelegate:(id< EOCNetworkFetcherDelegate>)delegate
{
_delegate = delegate;
// Instance method 'respondsToSelector:' notfound
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didFailWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}
果然,你以为你都看懂了,但是写起代码来漏洞百出。
遵循<NSObject>是为了确保实现了这个方法,这样在调用的时候就可以直接用这个方法检测是否能响应这个SEL了。
以下内容来自网络:
其实在ObjC 1.0 的时候,protocol 的这个 @optional 选项是不存在的,所有的 protocol方法都是必须实现的。所以不遵循<NSObject> 也没关系,只要判断指针是否存在然后直接调用就完了。但是ObjC 2.0 加入了 @optional特性,于是乎必须使用 的 respondsToSelector: 方法先做一次判断了。
由此可知,我手中的这本书真的有蛮老了,也说明了一点,不要买实体技术书了,买电子书,买一本马上看,执行力要强。日新月异啊日新月异。
- 苹果推崇使用delegate吗?我没有找到相关文档,如果有,请告诉我。但是我搜到一篇文章
我翻译了一下:
我应该何时使用Blocks(Objective-C)和closure(Swift)或者是Delegate进行回调?
2013年7月11日 Joe Conway
当碰到这种问题,我会问我自己“苹果会如何做?”当然,我们知道苹果会如何做,因为文档本身就是一本设计模式使用指南,如果我们从不同的角度看它的话。
我们需要找到苹果选择在哪里使用代理,以及在哪里使用闭包。如果我们在他们的选择中找到模式,我们可以将一些规则形式化,以帮助我们在自己的代码中做出相同的决定。
弄清楚苹果在哪里使用委托非常简单:在文档中搜索术语“委托”,我们将得到大多数使用委托的类。
寻找苹果使用闭包的地方有点困难。然而,苹果在声明方法时很擅长命名约定(顺便说一句,这是精通的一项基本技能)。例如,希望将字符串作为参数的方法在选择器中具有“string”,例如 initWithString:, dateFromString: 或者 startSpeakingString:。
当一个苹果方法需要一个闭包时,它将具有“handler”或”completion”。我们可以在文档中搜索这些术语,以在标准iOS API中建立合理的块使用列表。
以下是我的一些观察结果:
- 大多数委托协议都有一些消息
我现在在看GKMatch。我看到一条消息,说明何时从另一个播放机接收数据、播放机更改状态、出现错误以及何时应该重新邀请播放机。这些都是不同的事件。如果苹果在这里使用闭包,他们会有两种选择。第一,他们可以为每个事件注册一个闭包。如果有人在Swift中这么做的话,他们可能是一个混蛋。
另一个选择就是只创建一个可以接受所有可能输出的闭包。
varmatchBlock: (_ eventType: GKMatchEvent, _ player: GKPlayer?, _data: Data?, _ error: Error?) ->Void
这既不方便也不能做自文档化,所以你永远不会看到这种方法。好吧,你可能会看到这种方法,但是会有那么多令人眼花缭乱的代码行,你不会有精力去关注这一行有多糟糕。
因此,我们可以说“如果一个对象有多个不同的事件,请使用委托”。
-
一个对象只能有一个委托
因为一个对象只能有一个委托,所以它实际上只能与该委托进行对话,而不能与其他任何委托进行对话。让我们看看CLLocationManager。当找到一个位置时,位置管理器将会告诉一个对象(并且只有一个对象)。当然,如果我们需要多个对象来了解这些更新,我们可能会创建另一个位置管理器。
不过,如果CLLocationManager是个单例呢?如果我们不能创建CLLocationManager的任何其他实例,我们就必须不断地将委托指针交换任何需要位置数据的对象。(或者建立一个精心设计的广播系统,只有你自己才能理解。)所以,对单例来使用委托没有多大意义。
最好的例子是UIAccelerator。在早期版本的iOS中,单例加速度计实例有一个委托,我们不得不偶尔交换。这太愚蠢了,以至于在后来的iOS版本中发生了变化。现在,任何对象都可以将一个闭包附加到CMMotionManager,而不会阻止另一个对象接受更新。
这里我们可以说,“如果有一个对象是单例对象,我们就不能使用委托。” -
一些代理方法需要返回值
如果你查看一些委托方法(几乎所有的数据源方法),都会期望有一个返回值。这意味着代理对象正在请求某种状态。虽然闭包可以合理地维护状态,或者至少推断状态,但这实际上是对象的职能。
想想看。如果我问闭包“你觉得鲍勃怎样?”它只能做两件事:向捕获的对象发送消息,询问对象对鲍勃的看法,或者返回捕获的值。如果它返回一个对象的响应,我们应该绕过这个闭包直接问这个对象。如果它返回一个捕获的值,为什么不将这个值定义为该对象上的一个属性?
从这个观察中,我们可以说,“如果对象正在回调以获取更多信息,我们可能会使用委托”。 -
过程与结果
如果我看一下NSURLConnecitonDelegate和NSURLConnectionDataDelegate,我会看到一些消息,比如“我开始这样做”,“这是我目前所知的”,“我已经完成了这项工作”,或“亲爱的上帝,世界即将结束,
DEALLOC!DEALLOC!DEALLOC!”这些信息概述了一个过程,在该过程中,当事的委托将希望在每个步骤中得到通知。
当我查看处理程序和完成方法时,我看到一个包含响应对象和错误对象的闭包。没有任何类似“到目前为止我在这里,我还在工作”的交流。
因此,我们可以说委托更面向过程,而闭包更面向结果。如果你需要在一个多步骤的过程中得到通知,你可能需要使用委托。如果你只是想要你请求的信息(或者关于或许信息失败的详细信息),那么你应该使用闭包。(如果将其与第3点结合使用,你将认识到委托可以在所有这些事件中维护状态,而多个独立的闭包则不能。)
这让我想到了两点。首先,如果你选择对可能失败的请求使用闭包,那么应该只使用一个闭包。我见过这样的代码:
fetcher.makeRequest({ (result) in
// Do something with result
},error: { (error) in
// Do something with error
})
这比下面的代码要难读的多(我从不谦虚地认为):
fetcher.makeRequest { (result, error) in
if let result = result {
// Handle result
}else{
// Handle error
}}
当然,我应该补充一下,有一次有人对我说,“在Smalltalk中,我们会做前者,因为当我们应该使用对象或者闭包的时候,为什么要使用if语句?”或者类似的问题是为了炫耀他有多聪明。所以我给他看了这个:
progressBar.startAnimating()
fetcher.makeRequest({ (result) in
progressBar.stopAnimating()
// Do something with result
},error: { (error) in
// WHY ARE YOU TYPING THIS TWICE?!
//你为什么要敲两次这行代码?!!!
progressBar.stopAnimating()
// Do something with error
})
这实现了我教育他的目标和展示他有多聪明的目标。
- 速度(可能?)
当当前播放时间改变时,AVPlayer有一个回调。这听起来更像一个过程,而不是一个结果,所以通过第4点,我们将使用委托。但这个特定的回调使用了一个闭包。我猜这是为了提高速度----因为理论上,这个闭包可以每秒被调用数百次,所以消息查找可能很慢。
如果有疑问,请记录下来
我真的想不出一个好的总结标题,所以你看到的就是这个。(而且,我只是坐下来吃午饭,看起来五个人汉堡比聪明更重要。?Also, I just satdown to eat my lunch and it seemed like a Five Guys burger was more importantthan being clever.)这篇文章应该能给你在一些你自己的类中实现回调的好指南。如果你的案例没有被涵盖,我敢打赌在iOS API中找到类似的类可以回答你的问题。如果你所做的事情是如此的激进,以至于之前没人做过,那么试着两者都做,看看有什么效果。
网友评论