IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!
前言:
最近都在折腾 Sagit 架框的内存释放的问题,所以对这一块有些心得。
对于新手,学到的文章都在教你用:typeof(self) __weak weakSelf = self。
对于老手,可能早习惯了到处了WeakSelf了。
这次,就来学学,如何不用WeakSelf。
1:从引用计数器开始:
这里先设计一个TableBlock类:
@interface BlockTable : NSObject typedef void (^AddCellBlock)();
@property (nonatomic,copy)AddCellBlock addCell;@end
先这么简单,一个BlockTable只有一个block属性,然后输出一段释放的日志。
-(void)dealloc
{
NSLog(@"Table relase");//relase为错误字,为了和下图保持一致的错别字,这里就不改了。
}
接着,随意找一个地方写写代码:来new了一个BlockTable,并打印一下信息:

这时候它的引用数是1,并且出了Table relase 。
接着给addCell属性赋一个值,并运行:

一个空的事件,里面并没有引用到table,所以引用数还是1。
2:开始循环引用
在block引用table,让它产生循环引用,并运行:

我们看到:引用数变成了3,没有输出对象释放信息了,为啥不是2呢?大大的问号!!
一个属性赋值,为什么增强两个引用计数?
3:猜解跳跃的计数器
接下来,把属性设置为nil,运行看看:

设置为nil,还有2?
也正常释放了?
为了证实自己对这个看起来就很明显的猜想:重写addCell的setter方法,不进行任何保存:
@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{ }
同时去掉置为nil的代码:再运行看看:

计数器仍为2,而且也释放了。
经过思考,出来了以下的结论:
:块的定义本身,就会造成1次引用,不过这次引用,在块离开所在的函数时,释放时,抵消掉引用数。 :存档块的时候,会造成1次引用,而这个引用,是内存无法释放的原因。
4:根据上述解释,得到一个疯狂的结论:
只要block的代码只执行1次的,都可以任性的self或其它强引用。 事实上,我们写的代码,很多block的确只执行一次,不管是传的时候就执行,还是传完之后过段时间回调再执行。 认定只要执行1次的,就不需要WeakSelf,除非第三方框架的设计者造孽留坑,忘了在存档block执行后补上block=nil这一刀。
5:消灭赋值的引用计数:
继续发挥想象力,既然存的时候,会增加一次引用,辣么,让它不增加引用不就好了:
@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{
__weak AddCellBlock addCellWeak=addCell;
_addCell=addCellWeak;
}
我们先给这个block定义一个弱引用,然后再赋值给_addCell,运行看看:

哇草,成功了!计数器为2,正常释放了,看来自己的想象力,还是可以的!!
接下来,我们补充完善一下代码,增加一个reloadData方法,方法里调用事件。
完整的代码如下:
@interface BlockTable : NSObject typedef void (^AddCellBlock)();
@property (nonatomic,copy)AddCellBlock addCell; -(void)reloadData;
@end @implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{
__weak AddCellBlock addCellWeak=addCell;
_addCell=addCellWeak;
}
-(void)reloadData
{
if(self.addCell)
{
self.addCell();
self.addCell();//没事来两次,模拟table多次循环清加cell
}
}
-(void)dealloc
{
NSLog(@"Table relase");
}
@end
修改一下增加日志输出,现在再执行一下看看:

一切看起来都相当完美,不需要引入第三,需要多次使用的,只是在存的时候,存个弱引用,就搞定了。
6:弱引用降低计数的缺陷:
块的定义,和使用的场景,必须在同一个函数。 说白了就是块离开函数体就会消亡,所以要用要赶紧,且用且珍惜。
正常一个Table写完代码reloadData后,数据出来了。
但如果后面还跟有一个刷新重新加载的功能?
而这个重新调用reloadData的地方,可能跟block不在同一个函数,比如代码像这样:
-(void)start
{
BlockTable *table=[BlockTable new];
self.table=table;//搞到全局变量中
table.addCell = ^{
__weak typeof(table) this=table;
NSLog(@"addCell call");
};
[table reloadData];
NSLog(@"table retain = %ld",CFGetRetainCount((__bridge CFTypeRef)(table)));
}
-(void)reflesh
{
[self.table reloadData];
}
给外面的类定义了一个table属性,然后调用完start后再调用reflesh,运行,会怎样呢?

出现了IOS上最可怕的EXC_BAD_ACCESS 野指针错误。
对于block离开函数后,消亡了容易理解,只是这里:
这什么是直接抛异常?哥不是作了判断了么?
让我们换种代码写法:

另外从上图看:_addCell还是有值的。
为什么if(self.addCell)判断就直接死,if(_addCell)却没死呢? 正常self.addCell正常不是也return _addCell么? 这个问题,留给让你们思考了。
最可怕的,还是下面的这段话:

7:避开野指针,仍是弱引用,功能不变
OK,继续发挥想象力,看看怎么避开野指针,同时还是实现上述的效果:
1:把block属性从copy改成weak
@property (nonatomic,weak)AddCellBlock addCell;
2:赋值代码手工copy:
-(void)setAddCell:(AddCellBlock)addCell
{
addCell=[addCell copy];
_addCell=addCell;
//_addCell=[addCell copy];这样简写是不行的,不明白为虾米呢 // 原来是这样写的:
// __weak AddCellBlock addCellWeak=addCell;
// _addCell=addCellWeak ;
}
再次运行,神奇的事情发生了:

流程还是很顺,不会有野批针异常,Table也释放了。
唯一的遗憾,就是跳出函数后,block不能再复用了:

8:block的copy方法:
对于默认传进来的block(有三种形态:全局、栈、堆)
全局 copy 还是全局 堆 copy 还是堆 栈 copy 变成堆
说白了,copy只对类型是栈是才有效。
这是因为:栈的block,在执行完后出括号后,直接是销毁对象。
如果有弱引用过去,会造成野指针。
而其它两种类型,销毁时,会将指针指向一个空指针。
addCell=[addCell copy] 和默认copy的属性 _addCell=addCell 也是执行了copy操作。
执行后,addCell的类型就变成堆形态,这样销毁的时候,是空指针。
9:空指针和野指针的区别:
空指针:指向一个:人为创造的一个指针,它的名字叫空,有座空房子,里面什么也没有。 野指针:就是指向的都不知哪去了,连空房子都木有。
10:扩展想象力,如何消灭引用数,还能长久保留?
弱引用的坏处,就是block出了函数,就不再可用这个block了。
那还能怎么办呢?没事,我还有想象力!!!!!
如果block可以重建呢?
比如:
:将block转成字符串存档,适当时机还原回来重新赋值 :将block序列化保存,适当时机还原回来? :runtime读取block的__FuncPtr,存档再动态创建?

伪代码大体如下:
-(void)setAddCell:(AddCellBlock)addCell
{
addCell=[addCell copy];
_addCell=addCell; //_addCell=[addCell copy];这样简写是不行的,不明白为虾米呢 // 原来是这样写的:
// __weak AddCellBlock addCellWeak=addCell;
// _addCell=addCellWeak ; //存档block的字符串
}
-(void)reloadData
{
if(!_addCell)
{
//从存档的block字符串还原block
//_addCell=还原block
}
if(_addCell)
{
_addCell();
_addCell();
}
}
那么就剩下两个问题?
:怎么把block存档? :怎么将存档数据还原成block。
对搞C#的来说,这些都家常便饭,oc这块还不熟,有路过的朋友可顺路给支支招!!
11:如果第10的方式解决不了,就只能,只能,引入时机第三者了
不过这个引入第三者,只是一个时机切入点,在这个时机触发的时候,将其中的一方的引用设置为nil。
像Sagit框架的布局方面的时机,就选在导航回退等事件中处理。
不过这里需要一个小技巧:
在存档block时,不一定要存在当前对象,也可以用一个统一的全局block管理起来。
这样在业务处理时,根据业务情况,从全局block里来移除某些block即可。
具体取决于业务,所以这个就不展开了。
总结:
相信,一路看下,看懂了,后续的情况,基本上已经用不上WeakSelf这东西了,因为像一个block,其生命周期必须和持有者保持一致的,还是挺少的。
而这种少的情况,如果第10步解决了,基本就全都解决了,解决不了,还有11。
相信读完此文,如果能完全理解,你就再也看不到block前WeakSelf这种,WeakSelf也没有存在必要了。
最后,欢迎大伙关注IT连创业,虽然最近我都在折腾IOS,哈哈。
不过IOS基础还是要打劳,后续产品改进起来才有质的飞跃。
IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!的更多相关文章
- 看懂此文,不再困惑于 JS 中的事件设计
看懂此文,不再困惑于 JS 中的事件设计 今天刚在关注的微信公众号看到的文章,关于JS事件的,写的很详细也很容易理解,相关的知识点都有总结到,看完就有种很舒畅的感觉,该串起来的知识点都串起来了.反正一 ...
- 一篇文章看懂iOS代码块Block
block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量.作为参数.作为返 ...
- iOS开发——OC基础-ARC、BLOCK、协议
一.ARC ARC 是一种编译器特性!而不是IOS运行时特性,和JAVA中得垃圾回收机制完全不一样ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的 ...
- 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(下)- block中任性用self
前言: 在处理完框架内存泄漏的问题后,见上篇:讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(中)- IOS不为人知的Bug 发现业务代码有一个地方的内存没释放,原因很也简单: ...
- 怎样看懂Oracle的执行计划
怎样看懂Oracle的执行计划 一.什么是执行计划 An explain plan is a representation of the access path that is taken when ...
- [转]看懂ExtJS的API
原文地址:http://www.cnblogs.com/youring2/archive/2013/03/05/2944004.html ExtJS的功能很强大,相应的其API也很庞大,并且看起来并不 ...
- iOS深入学习(再谈block)
之前写过一篇博客,把Block跟delegate类比,说明了使用block,可以通过更少的代码实现代理的功能.那篇博客将block定义为类的property. 过了这么长时间,对于block的内容有了 ...
- 【 全干货 】5 分钟带你看懂 Docker !
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者丨唐文广:腾讯工程师,负责无线研发部地图测试. 导语:Docker,近两年才流行起来的超轻量级虚拟机,它可以让你轻松完成持续集成.自动交付 ...
- 看完此文还不懂NB-IoT,你就过来掐死我吧...【转】
转自:https://www.cnblogs.com/pangguoming/p/9755916.html 看完此文还不懂NB-IoT,你就过来掐死我吧....... 1 1G-2G-3G-4G-5G ...
随机推荐
- Linux中MySQL配置文件my.cnf参数优化
MySQL参数优化这东西不好好研究还是比较难懂的,其实不光是MySQL,大部分程序的参数优化,是很复杂的.MySQL的参数优化也不例外,对于不同的需求,还有硬件的配置,优化不可能又最优选择,只能慢慢的 ...
- JSON数据解析及gson.jar包
从服务器端接收数据的时候,那些数据必须以浏览器能够理解的格式来发送. 服务器端的编程语言只能以如下 3 种格式返回数据: HTML XML JSON JSON一种简单的数据格式,比xml更轻巧. JS ...
- jenkins+github持续集成中的坑
1.前言 刚开始开发自己的独立博客的时候,每次发布都要手动打包,上传服务器,杀tomcat进程,重启,来回这么重复性工作,很快就有点不耐烦了.如果能自动化的东西,就绝不要手动了,所以自己搭建了个持续集 ...
- 两个port贴合七夕主题,百度输入法的“情感营销”策略
一年一度的七夕佳节是情侣.夫妻之间传情达意.诉说衷肠的最佳时节.基于这一背景.一些传统企业.互联网公司也会针对性的推出一些营销策划,使产品和服务更贴近用户需求,更"接地气" ...
- openstack-glance API 镜像管理的部分实现和样例
感谢朋友支持本博客,欢迎共同探讨交流,因为能力和时间有限.错误之处在所难免.欢迎指正. 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/qq_21398167 原博文地 ...
- 【Notification】屏蔽特定应用的通知提示
须要默认屏蔽特定app的通知提示 设置app是否接收通知的界面 点击每一个条目进去的界面 AppNotificationSettings extends SettingsPreferenceFragm ...
- Node.js开发Web后台服务
一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...
- iBATIS使用$和#的一些理解
我们在使用iBATIS时会经常用到#这个符号. 比如: sql 代码 select * from member where id =#id# 然后,我们会在程序中给id这个变量传递一个值,iBATIS ...
- .Net Ajax跨域请求实现
下一阵子要做一个网站Web储备一下知识,AJAX 实现跨域请求,估计会用到,以前在学 WebServer 时候老师整理的一个文档,现在便于查阅和使用现在放到我的博客中. 一般平时我写web页面的时 ...
- 详解功能版本管理之使用eoLinker
先看一个对话: "这里,你改一下,这里返回一个object." "好...好......" "还有这里,返回个String." ...... ...