浅析Block闭包 (2)

浅析Block闭包

因为a、b、c都被该代码段所强引用,所以retainCount初始化都为1,又因为它们互相强引用,所以在连成环的时候retainCount都变为了2。这时候在代码段中,无论是哪一个对象先从代码段中释放,即retainCount--,都仍然还剩1。当整个代码段执行完后,三个类对象a、b、c的retainCount都从2减为了1,在整个系统中,再也没有其他影响因素会让它们的retainCount减少为0,这样就会导致这三个对象在运行中永不释放,从而造成内存泄漏。

在使用Block时也会很容易造成这个现象,当在网络异步的handler块中,我们通常会将当前ViewController中的某个网络数据属性捕获到handler中,在网络连接成功后将其进行赋值,这样就相当于Block块间接地强引用了当前VC,而通常来说,VC肯定会强引用下载器,而下载器中的Block块一般也会做为其属性进行强引用。如下图所示:

浅析Block闭包

为了解决强引用环的问题,可以通过将任意一个连接处断开即可。

断开1:基本不可能,在开发中在ViewController或者时ViewModel中都会将下载器作为属性而非临时变量,因为在调取过程中会一般会根据当前下载状态来进行下一步操作。

断开2:

方法一:不将_downloadHandler作为属性,而是使用临时Block变量,通常这么做的情况是因为下载器类不需要多次使用该block,对于复杂的下载器,这种策略很难得以保证。

方法二:(推荐)在下载操作结束后调用的方法中令 self.downloadHandler = nil,只要下载请求执行完毕,_downloadHandler属性就不再强引用该block,就打破了强引用环。

断开3:

方法一:因为Block强引用了VC的data属性,实际上也就强引用了VC(self),所以我们可以通过: __weak typeof(self) weakSelf=self将当前VC,即self弱引用化,生成一个名为weakSelf的当前vc对象,然后在block中使用 weakSelf.data=_data来进行调用。

方法二:方法一中大部分情况不会出现问题,但是当block块中有延时操作,而对_data的处理也在延时操作当中时,就会出现问题了,例如:

[self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //延迟2s获取data数据 weakSelf.data = onlineData; NSLog(@"%@",weakSelf.data); }); } else{ //handle error } }]; //假设成功从网络上获取到data //打印为空

这时候就会发现,无论是weakSelf还是self的data属性都为空。这就是因为在block执行完后(延时函数还未执行完),weakSelf所在的弱引用表已经被除名了,虽然延时函数还在执行。这时候当2s过后,weakSelf已经变为了nil,对nil发送getter消息也不会报错,所以这里就会出现取值为空的情况。

为了解决这一问题,只需要在block内再将weakSelf在代码段内部强引用化(该强引用仅限于Block内部)。例如:

[self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success //将weakSelf强引用化生成该代码段的strong变量 __strong typeof(self) strongSelf=weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //延迟2s获取data数据 //这里使用strongSelf临时变量 strongSelf.data = onlineData; NSLog(@"%@",strongSelf.data); }); } else{ //handle error } }];

这里的strongSelf属于临时变量,会加到该代码段(Block内)的autoreleasepool当中,当该处代码段结束时会自动释放掉,所以也就不会出现强引用情况。

方法三:使用临时变量充当当前VC(self),如下:

__block XXXViewController* vc = self; //这里self的retainCount会+1 [self.myDownloader downloadWithBlock:^(NSData *onlineData, NSError * _Nullable error, BOOL succeeded){ if(succeeded){ //download success vc.data = onlineData; //这里需要注意将该临时变量置为nil,即将retainCount重新减为1 vc=nil; } else{ //handle error } }];

这里需要注意在赋完值后必须将该临时变量重新置为nil,即将retainCount减1,否则仍会出现强引用的问题。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppwjf.html