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

此刻它的引用数是1,并且出了Table relase 。

随着给addCell属性赋三个值,并运转:

图片 2

二个空的风浪,里面并从未引用到table,所以引用数如故1。

2:初叶循环引用

在block引用table,让它发出循环引用,并运营:

图片 3

咱俩看到:引用数成为了3,没有出口对象释放消息了,为何不是2啊?大大的问号!!

2个属性赋值,为何增强五个引用计数?

3:猜解跳跃的计数器

接下去,把品质设置为nil,运营看看:

图片 4

设置为nil,还有2?

也不奇怪释放了?

为了声明本身对那一个看起来就很强烈的可疑:重写addCell的setter方法,不开展其他保存:

@implementation BlockTable
-(void)setAddCell:(AddCellBlock)addCell
{

}

并且去掉置为nil的代码:再运转看看:

图片 5

计数器仍为2,而且也释放了。

经过思考,出来了以下的定论:

1:块的定义本身,就会造成1次引用,不过这次引用,在块离开所在的函数时,释放时,抵消掉引用数。

2:存档块的时候,会造成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,运转看看:

图片 6

哇草,成功了!计数器为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

修改一下充实日志输出,现在再履行一下看看:

图片 7

一切看起来都一定完美,不必要引入第①,须求反复选取的,只是在存的时候,存个弱引用,就化解了。

6:弱引用下落计数的先天不足:

块的定义,和使用的场景,必须在同一个函数。

说白了就是块离开函数体就会消亡,所以要用要赶紧,且用且珍惜。

常规2个Table写完代码reloadData后,数据出来了。

但万一前面还跟有3个刷新重新加载的成效?

而那一个重新调用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,运维,会什么呢?

图片 8

出现了IOS上最吓人的EXC_BAD_ACCESS 野指针错误。

对于block离开函数后,消亡了便于了然,只是那里:

那什么是直接抛非常?哥不是作了判断了么?

让我们换种代码写法:

图片 9

别的从上图看:_addCell照旧有值的。

为什么if(self.addCell)判断就直接死,if(_addCell)却没死呢?

正常self.addCell正常不是也return _addCell么?

这个问题,留给让你们思考了。

 

最吓人的,依然上边的这段话:

图片 10

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 ;
}

再次运营,神奇的作业时有发生了:

图片 11

流程照旧很顺,不会有野批针至极,Table也释放了。

唯一的遗憾,就是跳出函数后,block不能够再复用了:

图片 12

8:block的copy方法:

对于默许传进来的block(有三种造型:全局、栈、堆)

全局 copy 还是全局

堆 copy 还是堆

栈 copy 变成堆

粗略,copy只对项目是栈是才使得。

那是因为:栈的block,在实践完后出括号后,直接是绝迹对象。

假若有弱引用过去,会造成野指针。

而其他三种档次,销毁时,会将指针指向八个空指针。

addCell=[addCell copy] 和默认copy的属性 _addCell=addCell 也是执行了copy操作。

实践后,addCell的类型就改成堆形态,那样销毁的时候,是空指针。

9:空指针和野指针的不相同:

空指针:指向一个:人为创造的一个指针,它的名字叫空,有座空房子,里面什么也没有。

野指针:就是指向的都不知哪去了,连空房子都木有。

10:扩大想象力,如何消灭引用数,还能长久保留? 

弱引用的弊端,就是block出了函数,就不再可用这么些block了。

那还是能如何是好吧?没事,作者还有想象力!!!!!

一经block可以重建呢?

比如:

1:将block转成字符串存档,适当时机还原回来重新赋值

2:将block序列化保存,适当时机还原回来?

3:runtime读取block的__FuncPtr,存档再动态创建?

图片 13

伪代码大体如下:

-(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();
    }
}

那就是说就剩下八个难点?

1:怎么把block存档?

2:怎么将存档数据还原成block。

对搞C#的来说,那么些都不以为奇,oc那块还不熟,有路过的对象可顺道给支支招!!

11:若是第拾的法门化解不了,就不得不,只可以,引入时机第叁者了

不过这些引入第贰者,只是一个机会切入点,在那一个时机触发的时候,将其中的一方的引用设置为nil。

像Sagit框架的布局方面的机遇,就选在导航回退等事件中拍卖。

然而那里需求二个小技巧:

在存档block时,不肯定要设有当前目的,也足以用3个合并的大局block管理起来。

这么在作业处理时,根据作业景况,从全局block里来移除有些block即可。

具体取决于业务,所以那个就不举行了。

总结:

相信,一路看下,看懂了,后续的事态,基本上已经用不上WeakSelf那东西了,因为像贰个block,其生命周期必须和全部者保持一致的,照旧挺少的。

而那种少的景观,借使第八步消除了,基本就全都解决了,消除不了,还有11。

深信读完此文,假诺能完全清楚,你就再也看不到block前WeakSelf那种,WeakSelf也从未存在须求了。

最后,欢迎我们关心IT连创业,即使近来本身都在折腾IOS,哈哈。

而是IOS基础如故要打劳,后续产品校订起来才有质的快速。

相关文章