Block和Delegate的选择

原文:http://blog.stablekernel.com/blocks-or-delegates/

名词解释:

Block: Objective-C/Swift中对闭包(closure)的实现,广泛使用在回调上。
Delegate: Cocoa的基本设计模式之一,面向协议(protocol)的编程,广泛使用在回调和对象间传值。


译文:

在我上一篇博客发表之后,saambarat问了我一个很好的问题,“在需要回调是,什么时候使用block,什么时候使用delegate?”。

通常在这种情况下,我会问我自己,”苹果官方是怎么做的?”。我们当然可以知道苹果是怎么做的,通过阅读官方文档,因为它本身就是一个设计模式指南。

我们需要找出苹果官方在哪里使用了delegate,在哪里使用了block。如果我们在官方文档的选择中发现了一些规律,我们可以得出一些规律帮助我们在自己的代码里做选择。

(文档搜索过程略...)

下面就是我的一些发现

1.大多数的delegate protocol有若干个消息

以GKMatch类为例,其中包含若干种消息,如:接收到其他播放器传来的数据时、播放器改变状态时、发生错误时和当播放器需要重置时,它们是不同的消息。如果苹果在此处使用block,那么有两个选择,一种方式是为每一个事件注册一个Block。如果有人写了类似这样的代码,那真的会很糟糕。

另一种方式就是创建一个block,接受所有的可能的输出,类似


Swift:

var matchBlock:(eventType:GKMatchEvent,player:Player,data:NSData,err:NSError)->Void

Objective-C:

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

这样的写法既不方便也不可读,所以你不会见到这种代码。如果你在别处遇到了这种代码,你也会被它搞得一脸懵逼。

所以,我们可以得出,如果一个对象有两个或以上不同的事件,使用delegation

2.一个对象只能用一个delegate

因为一个对象只能有一个delegate,并且他只能调用这一个delegate。我们考察CLLicationManager,这个location manager的类在定位成功时需要通知一个对象。当然,如果我们需要几个对象同时更新,我们可能要创建一个新的location manager

如果CLLocationManager是一个单例呢?这时候我们只能有一个location manager对象,所以我们考虑交换delegate的引用,指向任何一个需要location数据的对象(或者,发送一个只有你自己才知道的广播,给其他所有对象)。所以,在单例模式中使用delegate并不合适。

这种情况最合适的例子就是UIAccelerometer.在早期的iOS版本中,accelerometer的单例实例有一个delegate,我们需要时常改变delegate的引用。这实在是太愚蠢了,所以在之后的iOS版本中被替换了。现在,所有对象可以在CMMotionManager附加一个block,并且不影响其他对象接受回调。

所以,我们可以得出,如果一个对象是单例,不应该使用delegation

3.一些delegate期望得到返回值

如果你观察一些delegate方法(几乎所有的dataSource方法),它们都有一个期望的返回值。这意味着拥有delegate的对象正在请求某种状态。Block的运行机制决定它可以维持状态或者推断状态,就像一个对象(实际上block在底层就是一个对象)。

请想一想,如果我请求一个block“你觉得Bob这个人怎么样?”,它只能做两件事:返回Bob这个对象,或者返回询问Bob的结果。如果它返回的是Bob这个对象,我们实际上可以省略block而直接去获取Bob对象;如果它返回的是询问Bob的结果,这不应该作为Bob对象的一个属性么?

从这个角度看,我们可以得出,如果对象在回调时需要额外的信息,大多数情况下应该使用delegation

4.过程VS结果

如果我们观察NSURLConnectionDelegate和NSURLConnectionDataDelegate,我们看到的消息就类似”我正开始做某某事”,”我只知道这么多”,”我做完了这件事”,”上帝我就要析构了,我要狗带了~”。这些消息都概述了,这个方法应该在delegate的目标对象的什么过程中被调用。

因此我们可以说,delegate回调是更面向过程化的,而block回调是面向结果的。如果你仅仅是想获得你请求的信息(或是请求失败的错误信息),你应该使用block。(如果你结合第三点看,你会意识到delegate可以维持多种事件之间的状态,而多个单独的block则做不到。)

我又想到了两点。第一,如果你选择使用blocks来发起一个可能失败的request,你应该只使用一个block。我见过这样的代码:

Swift:

fetcher.makeRequest({
    result:AnyObject in
    /*do something with result*/
},error:{
    err:NSError? in
    /*do something with error*/
})

Objective-C:

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

上面的代码则比下面的代码更不可读

Swift:

fetcher.makeRequest(){
    (result:AnyObject,err:NSError?)in
        if let error = err {
            //handleresult
        }else{
            //handle error
        }
}

Objective-C:

[fetcher makeRequest:^(idresult, NSError *err) {
    if(!err) {
        // handle result
    } else {
        // handle error
    }
}];

当然,有些自作聪明的人会问我,你这样做,不就是把两个block用if表达式的形势合并成了一个block啊,好像并没有什么用诶。他们觉得自己是个天才,直到我给他们看了下面的

Swift:

progressBar.startAnimating()
fetcher.makeRequest({
    result:AnyObject in 
    progressBar.stopAnimating()
    /*do something with result*/
},error:{
    err:NSError in
    /*为什么你这里要写两句一模一样的代码!*/
    progressBar.stopAnimating()
    /*do something with error*/
})
Objective-C:
[progressBar startAnimating];
[fetcher makeRequest:^(id result) {
    [progressBar stopAnimating];
    /* do something with result*/
} error:^(NSError *err) {
    /*为什么你这里要写两句一模一样的代码!*/
    [progressBar stopAnimating];
    /* Do something with error */
}];

5.效率

AVPlayer有一个回调是用作当前的播放时间改变时调用。这更像是一个过程而不是一个结果,所以根据第4点,我们应该使用delegation。但是这个回调使用的却是block,我猜这是因为效率原因,因为block可以一秒钟被调用、回溯非常多次,而消息查找可能会比较慢。(作者这一点有待商榷,block通过压栈的方式达到保存状态和变量的目的,而delegate只是增加一个引用,理论上block的开销要比delegate更大。)

不清楚的地方就看官方文档

希望这篇文章能给你提供一些回调的实现方式的一些指导。如果你遇到的情况这里没有提到,我打赌在iOS API里找一个类似的类会回答你的疑问。如果你觉得真的遇到了从来没人碰到过的情况,再试一次,你就会得到答案 :-)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容