美文网首页Effective Objective-C 2.0
何时使用block或delegate进行回调

何时使用block或delegate进行回调

作者: 蜗牛你慢慢来 | 来源:发表于2019-07-24 14:33 被阅读0次

    ​第23条:通过委托与数据源协议进行对象间通信

    1. 此模式可将数据与业务逻辑解耦。例如,用户界面有一个UITableView,那么此视图只应包含显示数据所需的逻辑代码,而不应决定要显示何种数据以及数据之间如何交互等问题。
    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    
    1. 当某个委托方法需要被调用多次的时候,可以使用含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。
    @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: 方法先做一次判断了。

    由此可知,我手中的这本书真的有蛮老了,也说明了一点,不要买实体技术书了,买电子书,买一本马上看,执行力要强。日新月异啊日新月异。

    1. 苹果推崇使用delegate吗?我没有找到相关文档,如果有,请告诉我。但是我搜到一篇文章

    我翻译了一下:

    我应该何时使用Blocks(Objective-C)和closure(Swift)或者是Delegate进行回调?
    2013年7月11日 Joe Conway
    当碰到这种问题,我会问我自己“苹果会如何做?”当然,我们知道苹果会如何做,因为文档本身就是一本设计模式使用指南,如果我们从不同的角度看它的话。
    我们需要找到苹果选择在哪里使用代理,以及在哪里使用闭包。如果我们在他们的选择中找到模式,我们可以将一些规则形式化,以帮助我们在自己的代码中做出相同的决定。
    弄清楚苹果在哪里使用委托非常简单:在文档中搜索术语“委托”,我们将得到大多数使用委托的类。
    寻找苹果使用闭包的地方有点困难。然而,苹果在声明方法时很擅长命名约定(顺便说一句,这是精通的一项基本技能)。例如,希望将字符串作为参数的方法在选择器中具有“string”,例如 initWithString:, dateFromString: 或者 startSpeakingString:。
    当一个苹果方法需要一个闭包时,它将具有“handler”或”completion”。我们可以在文档中搜索这些术语,以在标准iOS API中建立合理的块使用列表。
    以下是我的一些观察结果:

    1. 大多数委托协议都有一些消息
      我现在在看GKMatch。我看到一条消息,说明何时从另一个播放机接收数据、播放机更改状态、出现错误以及何时应该重新邀请播放机。这些都是不同的事件。如果苹果在这里使用闭包,他们会有两种选择。第一,他们可以为每个事件注册一个闭包。如果有人在Swift中这么做的话,他们可能是一个混蛋。
      另一个选择就是只创建一个可以接受所有可能输出的闭包。
    varmatchBlock: (_ eventType: GKMatchEvent, _ player: GKPlayer?, _data: Data?, _ error: Error?) ->Void
    

    这既不方便也不能做自文档化,所以你永远不会看到这种方法。好吧,你可能会看到这种方法,但是会有那么多令人眼花缭乱的代码行,你不会有精力去关注这一行有多糟糕。
    因此,我们可以说“如果一个对象有多个不同的事件,请使用委托”。

    1. 一个对象只能有一个委托
      因为一个对象只能有一个委托,所以它实际上只能与该委托进行对话,而不能与其他任何委托进行对话。让我们看看CLLocationManager。当找到一个位置时,位置管理器将会告诉一个对象(并且只有一个对象)。当然,如果我们需要多个对象来了解这些更新,我们可能会创建另一个位置管理器。
      不过,如果CLLocationManager是个单例呢?如果我们不能创建CLLocationManager的任何其他实例,我们就必须不断地将委托指针交换任何需要位置数据的对象。(或者建立一个精心设计的广播系统,只有你自己才能理解。)所以,对单例来使用委托没有多大意义。
      最好的例子是UIAccelerator。在早期版本的iOS中,单例加速度计实例有一个委托,我们不得不偶尔交换。这太愚蠢了,以至于在后来的iOS版本中发生了变化。现在,任何对象都可以将一个闭包附加到CMMotionManager,而不会阻止另一个对象接受更新。
      这里我们可以说,“如果有一个对象是单例对象,我们就不能使用委托。

    2. 一些代理方法需要返回值
      如果你查看一些委托方法(几乎所有的数据源方法),都会期望有一个返回值。这意味着代理对象正在请求某种状态。虽然闭包可以合理地维护状态,或者至少推断状态,但这实际上是对象的职能。
      想想看。如果我问闭包“你觉得鲍勃怎样?”它只能做两件事:向捕获的对象发送消息,询问对象对鲍勃的看法,或者返回捕获的值。如果它返回一个对象的响应,我们应该绕过这个闭包直接问这个对象。如果它返回一个捕获的值,为什么不将这个值定义为该对象上的一个属性?
      从这个观察中,我们可以说,“如果对象正在回调以获取更多信息,我们可能会使用委托”。

    3. 过程与结果
      如果我看一下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
    })
    

    这实现了我教育他的目标和展示他有多聪明的目标。

    1. 速度(可能?)
      当当前播放时间改变时,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中找到类似的类可以回答你的问题。如果你所做的事情是如此的激进,以至于之前没人做过,那么试着两者都做,看看有什么效果。

    相关文章

      网友评论

        本文标题:何时使用block或delegate进行回调

        本文链接:https://www.haomeiwen.com/subject/oryxrctx.html