博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络回调:Block和Delegate的对比
阅读量:6948 次
发布时间:2019-06-27

本文共 7558 字,大约阅读时间需要 25 分钟。

场景

block和delegate是iOS开发者经常用到的技术,也常常出现在各种面试题里,你经常听到他们之间的对比。

我的态度是每个成熟的技术并没有明显的优劣,不应该用谁好谁劣来评判他们,而应该看谁更适合应用场景,在合适的场合选择合适的技术。

本篇文章将讨论在 网络层调用和回调 这个场景下的技术选择。

Block回调

一个常见的Block回调,通常是业务代码调用请求,然后在回调中获得返回的数据,然后执行业务逻辑,如下:

// 业务层代码- (void)blockDemo {	[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {	    // 处理业务逻辑代码	    // ...		NSLog(@"Result from block:%@", data);	}];}复制代码

考虑点1:内存

之所以会考虑这个问题,是因为用户使用时常常会刚进入一个页面,就立刻点返回,此时网络请求刚发出去,数据返回有可能还没回来,那么这个网络请求会怎么样呢?

当前页面controller能够被pop出栈,释放掉吗?毕竟网络请求还没结束

我们一一来验证

controller销毁

如何验证内存释放已经释放,很简单,首先我写的网络请求并不是真的网络请求,他只会延时5s返回一个假的数据,用来方便模拟网络请求

- (void)requestWithParms:(NSDictionary *)parms WithResult:(ResultBlock)result {		int delay = NET_DELAY; // 默认是5s,可以传参数改变		if ([parms valueForKey:@"delayTime"] != nil) {		delay = (int)[parms valueForKey:@"delayTime"];	}		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{		NSString *resultData = @"This is a Mock Data!!";		NSLog(@"Network Finish:%@",resultData);		if (result) {			result(resultData, nil);		}	});}复制代码

再在viewcontroller的dealloc方法里打印log,就能知道dealloc是否被调用,如果调用说明可以释放

- (void)dealloc {	NSLog(@"NextPageViewController has been dealloc!")}复制代码

这样验证起来就是很简单,只需要执行blockDemo方法,然后立刻点返回,退出当前vc,等5秒后如果看结果

结果是vc可以释放的

2018-03-25 19:36:07.345523+0800 NetworkCallback[4580:3600834] NextPageViewController has been dealloc!复制代码

Block捕获外界变量销毁

我们再进一步想想,Block有个最大的特点是可以访问当前的作用域,我们随便创建一个数组,重复上面操作,是否能够销毁

- (void)blockDemo {	NSArray *outsideArray = @[@1, @2, @3];		[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {		// 处理业务逻辑		// ...				NSLog(@"Result from block:%@", data);		NSLog(@"outsideArray :%@", outsideArray);	}];}复制代码

打印结果:

2018-03-25 19:55:40.997535+0800 NetworkCallback[4970:3641450] NextPageViewController has been dealloc!2018-03-25 19:55:44.831721+0800 NetworkCallback[4970:3641450] outsideArray :(    1,    2,    3)复制代码

神奇不?!

注意,vc先销毁了,但是5s后,这个临时变量竟然还没有销毁。那么这个变量存储在哪里呢?留个悬念

考虑一下,如果你在Block代码里访问了一个超大的文件,这个文件必然是保存内存的,然后此时你遇上了网络慢,接口好久没有返回,那么这个超大的文件就会一直占用内存

继续,如果我在Block中访问self呢?此时的self就是当前的controller,这时候可以销毁吗?

- (void)blockDemo {	NSArray *outsideArray = @[@1, @2, @3];	[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {		// 处理业务逻辑		// ...				NSLog(@"Result from block:%@", data);		NSLog(@"outsideArray :%@", outsideArray);		NSLog(@"self :%@", self);	}];}复制代码

打印结果:

2018-03-25 20:04:21.012135+0800 NetworkCallback[5224:3659292] Network Finish:This is a Mock Data!!2018-03-25 20:04:21.012476+0800 NetworkCallback[5224:3659292] Result from block:This is a Mock Data!!2018-03-25 20:04:21.013186+0800 NetworkCallback[5224:3659292] outsideArray :(    1,    2,    3)2018-03-25 20:04:21.013675+0800 NetworkCallback[5224:3659292] self :
2018-03-25 20:04:21.013858+0800 NetworkCallback[5224:3659292] NextPageViewController has been dealloc!复制代码

看到了吗?dealloc是最后打印出来的,也就是说Block不返回,controller就释放不了了!

看起来是在Block内访问谁,谁就无法释放啊!

有人会想这是不是就是循环引用呢?

请大家回忆一下:Block循环引用是self强引用Block,Block里面再强引用self,这里Block确实强引用了self,但是self并没有强引用Block,这个Block是一个参数传给了NetService,跟self并无关系

而且是循环引用的话,那么vc会一直释放不掉,但看上面的log,其实是可以释放的,只是释放的时机被延后了

但是,无论如何,我们试试换成 weakself 会怎么样呢? 打印结果:

2018-03-25 20:07:10.944557+0800 NetworkCallback[5294:3665537] NextPageViewController has been dealloc!2018-03-25 20:07:15.228961+0800 NetworkCallback[5294:3665537] Network Finish:This is a Mock Data!!2018-03-25 20:07:15.230836+0800 NetworkCallback[5294:3665537] Result from block:This is a Mock Data!!2018-03-25 20:07:15.231074+0800 NetworkCallback[5294:3665537] outsideArray :(    1,    2,    3)2018-03-25 20:07:15.231190+0800 NetworkCallback[5294:3665537] weakSelf :(null)复制代码

没问题,果然换成weakself就解决了

原因是什么?

为什么在Block内访问谁,谁就无法释放呢?为什么用weakself就解决了呢?

Block的本质是个对象

Block看起来像一个函数,其实在objectice-c中,它是个对象,之所以Block可以捕获外部变量,正是因为它是个对象,他有自己的属性,他用属性强引用了外部变量,导致外部变量(就是上面的self和outsideArray)的引用计数不为0,也就不能释放了

weakself做了什么

当Block中访问weakself的时候,强引用并没有指向self,而是指向weakself,所以self可以被释放

内存小结

  1. 使用Block无论是否有循环引用的可能,都要使用weakself,来防止vc被持有,而延迟释放
  2. Block会导致对象的生命周期被延长,特别是当某些大文件被Block访问时,有几率导致内存不足

考虑点2:代码安全

这是基于上面的考虑,我们已经知道要用weakself来保证controller被及时释放,也可以在上面log中看到weakself变成了nil,此时有可能导致crash,因为我们正在操作一个nil对象 想象一个业务场景:分页请求,你拉取了前面几页,比如page=3,然后去拉下一页数据,此时网络请求尚未返回,用户就退出当前页面,此时

  1. 如果页面能够被释放,那么Block中的业务逻辑代码被执行吗?
  2. 如果可以执行会有什么危险?

其实第一个问题上面的log已经回答了,log之所以被打印出来,其实就是Block中的代码被执行了嘛。

也就是说即使controller已经销毁,Block中的代码还是会被执行

第二个问题,执行了会有什么危险

  1. 通常这里会做json转model,会做某些数据转换,如果返回数据很大,比如是个三千多个元素的数组,那么势必浪费CPU去执行,注意,此时controller已经销毁了,执行代码是无意义的,这里的CPU是确确实实的浪费掉了。
  2. 想像一下,此时weakself是nil,如果是分页请求的数据,你通常是把新的数据加到某个数组里,然后你就crash了,因为你把nil加到数组去了
- (void)blockDemo {	__weak typeof(self) weakSelf = self;	NSArray *outsideArray = @[@1, @2, @3];	[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {		// 处理业务逻辑		// ...		[data addObject:weakSelf.pageArray]; // weakSelf是nil	}];}复制代码

因此,你必须小心翼翼,写上的保护代码

- (void)blockDemo {	__weak typeof(self) weakSelf = self;	NSArray *outsideArray = @[@1, @2, @3];	[self.service requestWithParms:nil WithResult:^(id data, NSError *error) {				// 保护代码		if (weakSelf == nil) {			return;		}				// 处理业务逻辑		// ...	}];}复制代码

代码安全总结

  1. Block会有执行无意义代码的可能,浪费CPU
  2. Block会有操作nil对象导致crash的可能,因此要写保护代码

Delegate回调

经过上面的验证,看起来好像Block有挺多麻烦了,那么delegate怎么样呢?我们也来试一试

首先是模拟网络请求,然后通过delegate回调

- (void)requestWithParms:(NSDictionary *)parms {	int delay = NET_DELAY;		if ([parms valueForKey:@"delayTime"] != nil) {		delay = (int)[parms valueForKey:@"delayTime"];	}		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{				// 判断成功		// 判断失败				NSString *resultData = @"This is a Mock Data!!";		NSLog(@"Network Finish:%@",resultData);				// ***这里加了判断***		if (self.delegate && [self.delegate respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {			[self.delegate networkFinishWithSuccess:resultData AndError:nil];		}	});}复制代码

注意我用***标注的注释,这里有个delegate的判断,这里保证了Block考虑点2不会出现,因为当delegate为nil的时候,绝对不会执行delegate的方法.

演示一下,代码如下

#pragma mark - Delegate request- (void)delegateDemo {	self.service.delegate = self;	[self.service requestWithParms:nil];}#pragma mark - NetWorkDelegate- (void)networkFinishWithSuccess:(id)data AndError:(NSError *)error {	NSLog(@"Result from Delegate:%@", data);}复制代码

验证block存在的问题

同样的,一进入Nextpage就立刻点返回,等5s看看代码会不会执行

2018-03-25 21:08:23.422103+0800 NetworkCallback[6399:3784145] NextPageViewController has been dealloc!复制代码

只有一条log,说明内存释放没有问题,而且在回调前对delegate的判断,使得我们非常方便的得知业务层是否还存在了,而如果用Block来实现就很麻烦,在网络回调前是无法得知的,一定要在Block里面加判断代码

总结:

  1. 在业务层delegate比Block更加优雅,可以在网络层回调前就中断逻辑,把错误发生的可能提前中断,而不必进入业务层才做判断,这是一个很好的隔断
  2. 没有延长某个对象生命周期,代码更加清晰,易于管理

delegate自己的问题

那么难道delegate就没有缺点了吗?

多个业务层请求

之前的demo中只有一个业务层,工程中绝对不会只有一个,而NetService的delegate只能指向一个对象,岂不是只有一个请求能够拿到回调,这岂不是滑天下之大稽?

当然不能这样,如果使用delegate,就必须对每个请求封装成一个对象,而不能统一的用一个NetService

@interface RequestAPI : NSObject@property (nonatomic, weak, nullable) id
delegate;/** 模拟Delegate请求方法 @param parms 请求参数 */- (void)requestWithParms:(NSDictionary *)parms;复制代码

可是每个对象都去实现一篇请求逻辑岂不是很傻?!

所以底层还是调用NetService

#import "RequestAPI.h"@implementation RequestAPI- (void)requestWithParms:(NSDictionary *)parms {	__weak typeof(self) weakSelf = self;	[[NetService alloc] requestWithParms:parms WithResult:^(id  _Nonnull data, NSError * _Nonnull error) {		if (weakSelf && [weakSelf respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {			[self.delegate networkFinishWithSuccess:resultData AndError:nil];		}	}];}复制代码

总结

所以其实,使用delegate和Block的结合使用,由此我们可以看出

  1. Block适合做集约型调用,每个业务逻辑不一样,但是我们可以通过把代码封装在Block中,然后发给统一的方法来处理,实现了统一方法处理不同的逻辑
  2. delegate适合离散型调用,每次返回是同样的逻辑
  3. 网络层调用要delegate和Block结合使用,在业务层回调适合delegate,在底层网络处理适合Block

参考:

转载地址:http://vpenl.baihongyu.com/

你可能感兴趣的文章
还没学到的技术总不想先去偷窥,时机成熟才敢去解开神秘的面纱。
查看>>
因特尔:视网膜屏将于2013年到来
查看>>
VC删除注册表键值项
查看>>
Hadoop安装配置使用
查看>>
1.01 与 37.8
查看>>
微积分35--二重积分的计算
查看>>
java对象与字符串之间的序列化和反序列化
查看>>
人工智障 2 : 你看到的AI与智能无关
查看>>
Let's Encrypt 使用教程,免费的SSL证书,让你的网站拥抱 HTTPS
查看>>
.net 面试题系列四(附答案)
查看>>
sql server的并发性
查看>>
windows php启动浏览器
查看>>
CPP_类模板与模板类
查看>>
用CocoaPods做iOS程序的依赖管理
查看>>
gallery图片展示(图片间隔)
查看>>
[下一个话题]利用NodeJs+Html5+WebSocket快速构建即时在线简易PPT
查看>>
如何在Exchange Server 2007集线器传输服务器角色上使用防垃圾邮件功能
查看>>
Redis持久化相关问题
查看>>
maven-war-plugin参数说明
查看>>
Qt学习之路(18): Qt标准对话框之QInputDialog
查看>>