知识罗列,以点成线带面
1、block 精髓:保存一段代码块
2、保存然后持有- 随时随地可以用
3、block的分类:1)NSGlobalBlock 全局静态block

2)NSMallocBlock

3) NSStackBlock

4、block是一个匿名函数,也是一个对象
5、通过内存地址来看所在的区域,栈是0x7为前缀,堆是0x6 ,静态变量是0x1开头的。
6、block可以自动捕获变量 。对象的底层是struct;
7、nslog是i/0流的,比较耗时。所以我们会在项目里加上debuglog,就是只是在debug的时候才会去打印。release的时候就不打印了。
8、关于循环引用,A持有B,B持有A。A什么时候被释放呢,就是收到了B发送的release信息,而B只有在收到release信号,retainCount - 1 = 0 调用 dealloc方法才会给A发送release信息。而B如果想要收到release信号必须得是A收到了B的release信号,retainCount-1=0,调用dealloc方法才会给B发送release信息。总而言之就是B要想收到release信号,必须得A收到release信号,而A要想收到release信号必须得B收到release信号,这样就会谁也收不到release信号,造成谁也无法释放,这就是循环引用。
9、像下面这样的,self->block->self->name self持有了block,而block持有了self下面的属性name,这样造成了循环引用这个VC就永远不会释放了,就会造成内存泄漏。

10、像如下这种情况虽然说可以解决循环引用的问题,但是打印不了name,这也是个问题。

这里面加了__weak 去修饰。这样的话就是 self->block—__weakSelf->name 其中block到__weakSelf的部分是弱引用,当进入vc又pop出去的时候self的引用计数是0,因为没有人强引用self了,block这里只是弱引用了__weakSelf并没有持有他。所以self就可以正常去释放了,之后block才会被释放。区别于上面的block强引用了self,当block没被释放的时候,self不能被释放。但是这里是__weakSelf释放了之后才打印name所以只会打印出一个null。这部分打印的代码如下

就是等两秒之后再调用block里面的代码。这里打印的就是null了,因为__weakSelf已经被释放了。但是如果没有延迟两秒则没有这个问题,因为只要vc加载了就会调用block代码块里的内容,这个时候self还没被释放就可以正常打印。
11、这里面如果在block里面加上一句把weakself赋值给strongself的话就能解决这个问题了。代码如下图

分析原因是在这个代码里strongself 是block代码块声明的一个属性 。其生命周期是这个block存在的周期。所以在下面执行self.block()这个方法的时候,虽然下面的打印name这个方法是延迟两秒再执行的。这个时候vc这个类已经pop出去了,
strongself是在block代码块里的局部变量,block在它的方法里引用了这个strongself那就是vc这个类的引用计数在block代码还没执行玩就还是1还不能被释放。所以vc 返回了之后。两秒之后打印了self.name之后。strongself被置为nil了。这个时候self的引用计数就是0了开始被释放了。这就完美的解释了先打印出name再执行dealloc方法。
12、那么除了这种方法之外,还有别的方法能够实现延迟两秒打印。比如下面这种

这里也是能在vc返回了之后两秒成功打印的。但是这里有一个问题就是vc的dealloc方法不会走,就是vc不会被释放。我们来分析一下。这里面__block 属性就是说我们block捕获了这个属性 self,也就是把selfcopy到了这个block的结构体里面作为一个属性了。所以vc虽然弹出去了,但是在block的生命周期里还不能被销毁,之后等下面这个打印的代码执行完了才有可能被释放。但是这里的捕获跟上面的strongself的引用不一样。加上__block的捕获可以理解为在生命周期里强引用而不是执行完这个方法就自动置为nil。所以我们打印完name之后vc并不是nil这样就形成了 self->block->vc->self->name 这样的循环引用。所以这里的捕获变量跟在block里面声明一个变量是有区别的。所以我们在这里nslog下面加上一行 vc=nil;之后就可以走dealloc方法了。由上面的持有链我们知道self->block->nil->self->name 这样的话强引用链就断掉了。
13、除了上面的方法之外我们还有别的方法也能实现这个name的打印。如下图

这里面的思路也很直接,既然我们要拿到这个vc的name那么我们直接把vc作为参数传进去不就好了。首先定义这样一个block typeof void(^jxBlock)(ViewController *vc) 这样的话下面的调用就是直接把self作为参数传进去了。这样的话既能打印self.name,dealloc方法也会执行。这里也可以简单分析一下,vc加载的时候我们调用这个block并把vc作为参数传进去了。然后vc准备释放了。但是vc作为参数传到block里面了。所以还不能释放,这就是vcpop出去了此时的引用计数还是1等到打印完了,这个时候vc自动被释放了(因为只是一个局部参数),vc = nil。那么这个时候self->block->nil->self就会都被释放了self被释放了就会走dealloc方法了。
14、来剖析一下下面这个最简单的block的情况,如下图 :

这里面也是可以直接说结论的就是当没有前面这个__block的时候是值传递,所以只能是only read 只读属性。有__block这个修饰的是指针传递,可读写。我们先来看看无block的底层c++代码

1)它就是一个简单的值传递,在结构体里面定义了一个int a ,就是上面那个定义的那个a。所以接下来我们可以打印这个值,但是只读。
接下来是有__block 的情况,这个地方就是指针传递了。如下图:

2)这个里面就是指针传递了,实际上这个__Block_byref_a_0 *a 这个也是一个结构体,里面包含了&a。就是a的地址,因此可以拿到a的地址和值,就可以对a进行任意读写操作了。
通过上面两段代码我们还是能够比较形象的看出来这个__block修饰符是在底层做了一个什么样的操作。
