block和delegate如何选择

【OC】开发中选择delegate 还是 block

By Joe Conway,CEO | Jul 11,2013 11:29:00 AM

译者注:译文已在 CocoaChina 上发布。

http://www.cocoachina.com/ios/20150927/13525.html

前文:网络上找了很多关于delegation和block的使用场景,发现没有很满意的解释,后来无意中在stablekernel找到了这篇文章,文中作者不仅仅是给出了解决方案,更值得我们深思的是作者独特的思考和解决问题的方式,因此将这篇文章翻译过来,和诸君探讨,翻译的很多地方不是很到位,望大家提出意见建议。

有人问了我一个很棒的问题,我把这个问题总结为:“开发过程中该选择 blocks or delegates ?当我们需要实现回调的时候,使用哪一种方式比较合适呢?”

一般在这种情况下,我喜欢问我自己:“如果问题交给 Apple ,他会怎么做呢?”当然,我们都知道 Apple 肯定知道怎么做,因为从某一层面上看, Apple 的文档就是一本用来指导我们如何使用设计模式的指导书。

因此我们需要去研究一下 Apple 分别是在什么情况下使用 delegate 和 block,如果我们发现了 Apple 做这种选择的套路,我们就可以构建出一些可以帮助在我们在自己的代码中做相同选择的规则。

如果要找出 Apple 使用 delegate 的场景很简单,我们只要搜索官方文档中的”delegate ”,就会获取到很多使用 delegation 的类。

但是搜索 Apple 中有关使用 blocks 的文档就有点困难了,因为我们不能直接搜索文档中的 ^ 。然而, Apple 声明方法时有很好的命名习惯(这也是我们精通IOS 开发的一项必备技能)。例如:一个以 NSString 为参数的方法,方法的 selector 就会有 String 字眼,像 initWithString ; dateFromString ; StartSpeaingString 。

当 Apple 的方法使用 block ,这个方法将会有” Handler ”,” Completion”或者简单的“ Block ”作为 selector ;因此我们可以在标准的 IOS API 文档中搜索这些关键词,用以构建一个可信任的 block 用例列表。

大多数 delegate protocols 都拥有几个消息源。

以我正在看的 GKMatch(A GKMatch object provides a peer-to-peer network between a group of devices that are connected to Game Center ,是 IOS API 中用来提供一组设备连接到 Game Center 点对点网络的对象)为例。从这个类中可以看到消息的来源分别是:当从其他玩家那接收到数据,当玩家切换了状态,当发生错误或者当一个玩家应该被重新邀请。这些都是不同的事件。如果 Apple 在这里使用 block ,那么可能会有以下两种解决方式:

一、可以对应每一个事件注册相应的block,显然这种方式是不合理的。( If someone writes a class that does this in Objective-C, they are probably an asshole. )

二、创建一个可以接受任何可能输入的 block

void (^matchBlock)(GKMatchEvent eventType, Player *player, NSData *data, NSError *err);

很明显这种方式既不简便又不易读,所以你可能从未看过这样的解决方案。如果你看过这样的解决方式,但是这显然是一个糟糕至极的代码行,你不会有精力去维护这个。

因此,我们可以得出一个结论:如果对象有超过一个以上不同的事件源,使用 delegation 。

一个对象只能有一个 delegate

由于一个对象只能有一个 delegate ,而且它只能与这个 delegate 通信。让我们看看 CLLocationManager 这个类,当发现地理位置后, location manager只会通知一个对象(有且只有一个)。当然,如果我们需要更多的对象去知道这个更新,我们最好创建其他的 location manager 。

这里有的人可能想到,如果 CLLocationManager 是个单例呢?如果我们不能创建 CLLocationManager 的其他实例,就必须不断地切换 delegate 指针到需要地理数据的对象上(或者创建一个只有你理解的精密的广播系统)。因此,这样看起来, delegatetion 在单例上没有多大意义。

关于这点,最好的印证例子就是 UIAccelerometer 。在早期版本的 IOS 中,单例的 accelerometer 实例有一个 delegate ,导致我们必须偶尔切换一下。这个愚蠢的问题在之后的 IOS 版本被修改了,现在,任意一个对象都可以访问 CMMotionManager block ,而不需要阻止其他的对象来接收更新。

因此,我们可以得出另一个结论:“如果一个对象是单例,不要使用 delegation

一般的 delegate 方法会有返回值

如果你观察一些 delegate 方法(几乎所有的 dataSource 方法)都有一个返回值。这就意味着 delegating 对象在请求某些东西的state(对象的值,或者对象本身),而一个 block 则可以合理地包含 state 或者至少是可以推断 state ,因此block 真正是对象的一个属性。

让我们思考一下一个有趣的场景,如果向一个 block 提问: What do you think about Bob? 。 block 可能会做两件事情:发送一个消息去捕获对象并询问这个对象怎么看待 Bob ,或者直接返回一个捕获的值。如果返回了一个对象的响应,我们应该越过这个 block 直接获取这个对象。如果它返回了一个捕获的值,那么这应该是一个对象的属性。

从以上的观察,我们可以得出结论:如果对象的请求带有附加信息,更应该使用 delegation

过程 vs 结果( Process vs. Results )

如果查看 NSURLConnectionDelegate 以及 NSURLConnectionDataDelegate ,我们在可以 protocol 中看到这样的消息:我将要做什么(如: willSendRequest ,将要发送请求)、到目前为止我知道的信息(如: canAuthenticateAgainstProtectionSpace )、我已经完成这些啦( didReceiveResponse ,收到请求的回复,即完成请求)。这些消息组成一个流程,而那些对流程感兴趣的delegate将会在每一步得到相应的通知。

当我们观察 handler 和完整的方法时,我们发现一个 block 包含一个响应对象和一个错误对象。显然这里没有任何有关“我在哪里,我正在做什么的”的交互。

因此我们可以这样认为, delegate 的回调更多的面向过程,而 block 则是面向结果的。如果你需要得到一条多步进程的通知,你应该使用 delegation 。

而当你只是希望得到你请求的信息(或者获取信息时的错误提示),你应该使用block。(如果你结合之前的三个结论,你会发现 delegate 可以在所有事件中维持 state ,而多个独立的 block 确不能)

从上面我们可以得出两个关键点。首先,如果你使用 block 去请求一个可能失败的请求,你应当只使用一个 block 。我们可以看到如下的代码:

[fetcher makeRequest:^(id result) {
   // do something with result
} error:^(NSError *err) {
    // Do something with error
}];

上面代码的可读性明显比下面 block 的可读性差(作者说这个是他个人不谦虚的观点,译者认为没有那么严重)

[fetcher makeRequest:^(id result, NSError *err) {
     if(!err) {
         // handle result
     } else {
        // handle error
     }
}];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在iOS开发中,可以使用Blockdelegate来实现一对多的回调。使用delegate时,首先需要定义一个delegate协议,其中包含回调方法。然后在需要进行回调的地方,设置delegate并调用回调方法。具体实现可以参考以下代码示例:\[2\] ``` // 定义delegate协议 protocol FirstCellDelegate: class { func firstCellBtnTap(_ cell: firstTableViewCell) } // 在需要进行回调的地方设置delegate并调用回调方法 weak var delegate: FirstCellDelegate? // 调用回调方法 if let delegate = self.delegate { delegate.firstCellBtnTap(self) } // 实现delegate回调方法 func firstCellBtnTap(_ cell: firstTableViewCell) { let cellIndexPath = tableView.indexPath(for: cell) print("delegate回调 section:\(cellIndexPath?.section ?? 0), row:\(cellIndexPath?.row ?? 0)") } ``` 另外,使用Block也可以实现一对多的回调。Block可以作为函数表达式传递给API,可以选择性地存储,并且可以被多个线程使用。Block不仅包含了在回调时需要执行的代码,还包含了执行期间所需的数据。具体实现可以参考以下代码示例:\[3\] ``` // 定义Block回调 typealias CallbackBlock = () -> Void // 在需要进行回调的地方使用Block进行回调 var callback: CallbackBlock? // 调用回调 callback?() ``` 通过使用Blockdelegate,可以实现一对多的回调,满足不同场景下的需求。 #### 引用[.reference_title] - *1* [Block实现iOS回调](https://blog.csdn.net/feelinghappy/article/details/119870367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [iOS BlockDelegate的用法,各自优缺点及使用场景](https://blog.csdn.net/huangshanfeng/article/details/82106580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值