简单对比了一下Delegate和Block的效率

最近公司的项目完成了,简单的写了个小demo来验证一下block和delegate的效率问题。

开始

开始之前还是麻烦直接先看一些结论吧!

终于,公司的项目给运营进行验证测试了,忙了那么久都没有时间学点新的东西。又倒腾了点新的东西出来,python啊,Go啊,然后感觉需要学习的东西还有很多,自己的能力也不够。买了几本书《Objective—C 高级编程 iOS与OS X多线程和内存管理》、 《重构》、《编程珠玑》….也没有时间看,刚刚才把OC的内存管理看了一遍。

一直在水各种qq群,然后有人在问block怎么用什么什么的,然后在群里就在讨论blockdelegate,然后说为什么很多第三方库都是用的delegate,apple自带的控件也全都是用的delegate

刚刚开始学block的时候,感觉block特别的难,什么鬼都不知道是怎么回事,代码索引也没有。所以我给出的第一个答案就是实用delegate至少在代码可读性上会好很多。

然后在简书看到一篇文章block和delegate傻傻分不清楚

由此我们可以看到delegate运行成本低,block成本很高。block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗。相对C的函数指针,只多做了一个查表动作 。

自己想了一下确实是这样的。

后来突然想到了自己一起踩过的坑,由于一直都有使用instrument或者MLeaksFinder来检查内存泄漏的习惯,所以在发现MJRefresh的时候发现内存泄漏,这个时候才发现之前使用block的时候并没有使用weakSelfstrongSelf 所以…
这就能够很好的解释为什么很多第三方库都会使用delegate而不是block了,毕竟对于目前的iOS市场,充斥着大量新手iOSer,如果使用block的话,第一会增加小白的使用成本,第二虽然不会立马造成crash,但如果不使用weakSelfstrongSelf的话,内存无法释放,对于那些写库的大神来说,应该是很难容忍的吧。

所以虽然我在项目中也会大量的使用block(主要是开发效率的提升,毕竟用delegate比较麻烦),但是对比delegateblockdelegate至少在以下三点比block好:

  1. 代码的可读性。
  2. 运行效率。
  3. 使用block需要时刻预防循环引用。

对于第四点,我想delegate作为一个很经典的设计模式,相比较于block出现的比较早。

作为iOS4的新特性,block最开始,现在可能也最常见的出现在UIView animateWithDuration 和 数组的enumeratedObjectsUsingBlock 方法中。

1

1
2
3
[UIView animateWithDuration:2.0f animations:^{
//do somethings
}];

2

1
2
3
[self.dataSource enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@,%@",obj,@(idx));
}];

第二种作为枚举遍历法,被广泛的使用,用以替换之前或者现在都用的 for forin两个循环办法,而这个方法查阅文档之后发现是遵循了NSFastEnumeration这个协议。而这个协议直有下面一个方法

1
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len;

这里面的实现原理,并不是本文的讨论范围,这里的block虽然能够代码巨大的效率提升,但也没办法跟delegate进行比较,而且这个应该也是算法的提升吧。如果对这个东西敢兴趣,也可以去看看巧大的文章,我记得他对这个有过深入的研究。

刚刚提到的三点,第一点可能没有办法去验证,第三点可以自己写一个block然后在这个block里面使用self然后再调用这个block,在看这个对象能不能dealloc

对于第二点,我大胆的写了一个demo,这也是我最常使用block的场景:通过tableviewcell上的button回调点击时间到controller中。

我在controller中添加一个tableview实现了一些基本的代码之后,为了验证这个问题,我有自定义了一个UITableViewCell

通过拖线

实现了一下两个方法,分别通过block 和delegate 回调了10000次。

1
2
3
4
5
6
7
8
9
10
- (IBAction)blockAction:(id)sender {
for (int i = 0; i < 10000; i++) {
self.blk();
}
}
- (IBAction)delegateAction:(id)sender {
for (int i = 0; i < 10000; i++) {
[self.delegate foocellDelegateButtonClicked];
}
}

在自定义cell的.h中声明了一个protocol

1
2
3
4
@protocol FooCellDelegate <NSObject>
@required
- (void)foocellDelegateButtonClicked;
@end

和一个block

1
typedef void (^blk_t)(void);

我们看一下整个.h文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <UIKit/UIKit.h>
typedef void (^blk_t)(void);
@protocol FooCellDelegate <NSObject>
@required
- (void)foocellDelegateButtonClicked;
@end
@interface FooCell : UITableViewCell
@property (nonatomic, copy) blk_t blk;
@property (nonatomic, weak) id<FooCellDelegate> delegate;
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@end

然后在controller中分别接受回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return ({
FooCell *cell = [FooCell cellWithTableView:tableView];
cell.delegate = self;
cell.blk = ^{
NSLog(@"block");
};
cell;
});
}
- (void)foocellDelegateButtonClicked {
NSLog(@"delegate");
}

代码部分写完了,然后简单的运行了一下,结果分别是

1
2
3
4
5
2016-08-10 14:53:00.466 IB[4649:179409] block
.
.
.
2016-08-10 14:53:02.926 IB[4649:179409] block
1
2
3
4
5
2016-08-10 14:53:50.639 IB[4649:179409] delegate
.
.
.
2016-08-10 14:53:54.325 IB[4649:179409] delegate

结果还是很直观的2.926 - 0.466 对比 54.325 - 50.639
我发现delegate的效率是要比block高些的。

接下来我又决定在block中使用一下外部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return ({
FooCell *cell = [FooCell cellWithTableView:tableView];
cell.delegate = self;
NSString *str = @"delegate";
cell.blk = ^{
NSLog(@"%@",str);
};
cell;
});
}
- (void)foocellDelegateButtonClicked {
NSString *str = @"delegate";
NSLog(@"%@",str);
}

由于没有对str进行更改,所以我并没有使用__block修饰str;

1
2
3
4
5
2016-08-10 15:38:41.340 IB[4717:198573] block
.
.
.
2016-08-10 15:38:44.136 IB[4717:198573] block
1
2
3
4
5
2016-08-10 15:39:20.837 IB[4717:198573] delegate
.
.
.
2016-08-10 15:39:23.883 IB[4717:198573] delegate

44.136 - 41.340 对比 23.883 - 20.837 2.796 vs 3.046
依然是block效率高于delegate

我再用__block修饰了一下,然后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return ({
FooCell *cell = [FooCell cellWithTableView:tableView];
cell.delegate = self;
__weak typeof(self) weakself = self;
cell.blk = ^{
__strong typeof(weakself) strongself = weakself;
NSLog(@"%ld",strongself->idx_);
strongself-> idx_++;
};
cell;
});
}
- (void)foocellDelegateButtonClicked {
NSLog(@"%ld",idx_);
idx_ ++;
}
1
2
3
4
5
2016-08-10 15:58:19.665 IB[4827:213165] 0
.
.
.
2016-08-10 15:58:22.866 IB[4827:213165] 9999
1
2
3
4
5
2016-08-10 15:58:42.689 IB[4827:213165] 0
.
.
.
2016-08-10 15:58:45.192 IB[4827:213165] 9999

22.866 - 19.665 对比 45.192 - 42.689 3.201 vs 2.503 delegate更高

对比了大半天才发现文中所谓的效率比较其实是几乎没有的,毕竟是回调了10000次才出现了那么微弱的差距。
所以前文对比的所谓的区别也是没有什么道理的。

但是可能考虑到运行顺序和可能出现的缓存等等其他可能出现的因素,我也不能下十分完整的定论,但是block作为一个在很多语言都具有的语法,其优越性对比delegate也不会差。

程序语言 Block的名称
C +blocks block
SmallTalk block
Ruby block
LISP Lambda
Python Lambda
C++ 11 Lambda
Javascript Anonymous function

写到这了,其实我也是比较方的,因为到现在也是刚刚才认可这个事情。

结论

所以到最后,无论block还是delegate 其实都是要看具体的应用场景的,如果脱离的具体的应用场景这样的最求所谓的性能优化其实也是没有什么意义的。所以一味的强行的去对比二者的区别也是没有多大的意义的。毕竟,如此微弱的差距,在排除了其他的因素之后,可能就完全的忽略不计了,在手机端,也不可能有生命回调什么的能够进行10000次。

所以除了可读性和需要预防循环引用。block带来开发效率的提升还是特别的快的。对于第三方库使用delegate的原因,我猜想还是因为使用delegate更贴近于原生的OC代码,毕竟是写出来给别人使用的,更原生,可能更友好。而我们更熟悉的AFNetworking 也是用的block回调网络请求的啊。

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%