#Block底层原理
##__block的内存管理
- 当block内部引用外部变量,block内部会去拥有这个对象。
- 一旦block从栈上拷贝到堆上,会调用block内部的一个copy函数,而这个copy函数就会调用一个block_object_assgin函数,对内部胸有的这个对象进行一个强引用或者弱引用。
- 当使用weak去访问,block内部修饰符变成weak,而且block依然是有拷贝到堆上的,还是会调用block内部的block_object_assgin函数,对weak修饰的对象进行一个引用操作,这个时候就是一个弱引用。
- 一旦block内部要访问对象的话,block底层结构__mian_block_impl_0里面的Desc就会多两个函数成员,一个copy一个dispose。
- block内部使用未修饰的变量时,里面牵扯不到访问外部的对象所以不需要内存管理相关的函数。
- 一旦加block修饰,block底层就会有多了两个内存管理相关的成员函数,就会调用block_object_assgin这个函数,对结构体里的对象进行操作,结构体内部会对block包装成的对象进行内存管理。
- 当block在栈上时,并不会对__block变量进行强引用。
- 假设blcok的内存如果是在栈上的,它并不会被block捕获到内部的__Block_byref_xx_0这个变量进行强引用。
- 一旦blcok被拷贝到堆上,它会调用blcok内部的mian_block_copy_0函数,内部又会调用block_object_assgin函数,block_object_assgin会对block形成强引用,相当于retain操作。
- block内部有一个捕获的成员变量block_byref_xx_0 *xx,block内部的这个变量的指针就会指向block_byref_xx_0这个结构体,指向__block变量包装成的一个对象,这个引用就是一个强引用,一旦blcok被拷贝到堆上的话,它内部的变量就会对这个对象形成强引用。
- block修饰的局部变量和内部引用外部变量的block,默认情况下是在栈上的。在ARC环境下面,一旦blcok是被强引用引用着的,系统会对栈上的block进行copy操作,这个时候block就会拷贝到堆上去。局部变量正常是在栈上面,那这个时候这个局部变量已经被捕获到了堆上去了,那block包装成的这个结构体对象函数里面的内容还是在栈上,那用堆上的变量去指向栈上的的结构体这样肯定是有问题的。
- 系统会帮我们做这么一个事情,假设有两个block0和block1,这两个block都用到了block变量,一开始他们都是在栈上,加入对block0进行copy操作,肯定会把它copy到堆上去,block0一旦拷贝到堆上会自动将block0内部使用到的block变量的内存也拷贝到堆上去,而且堆上的block0内部会对拷贝过来block变量形成一个强引用。如果block1也复制到堆上,那block1会对它当初内部使用到的block变量也是一个强引用,由于block变量上一次已经被拷贝到堆上面,所以这次block1拷贝到堆上面,block就不会再次拷贝到对上面了。
- 当block从内存中移除时会调用block内部的dispose函数,dispose函数内部又会调用Block_object_dispose函数,将这个对象的结构体传进去进行一次dispose操作,Block_object_dispose函数会自动释放引用的__block变量,相当于release操作。
- 比如说堆上的block强引用着block变量,一旦block销毁从堆中移除了,这个时候就会去释放这个block变量,这个时候两个就都销毁了。还有一种情况,两个block都强引用着一个block变量,那么其中一个block0释放了,那么block变量会做一次release操作,相当于一个强引用释放掉了,但是还有一个block1强引用着这个block变量,所以它并不能完全销毁。但是如果另外一个block1也销毁了,那么没有任何block引用block了,这次再进行一次release操作就可以达到一个完全释放的效果。
- 为什么要这样管理内存呢?首先block内部捕获的成员变量包装成一个对象block_byref_xx_0结构体内部有一个和对象类似的东西isa,这样明摆的就是一个对象,既然是一个对象的话,肯定要对它进行相应的内存管理。为什么block对它进行强引用要block对它进行管理呢?因为block_byref_xx_0这个结构体是直接在block内部使用的,既然是blcok内部使用的,block就要对它内存管理进行负责,所以由blcok决定什么时候把它放到堆上去,什么时候把它从堆中移除,这些都是由block决定的。
- 当blcok内部访问外部自动类型的对象,然后block拷贝到堆上的时候,就是调用block内部的main_blcok_copy_0函数,内部又会调用block_object_assgin函数,如果内部访问的是普通对象,它会根据外部捕获进到block内部的修饰符是strong还是__weak来决定是进行强引用或者弱引用。
- block能不能和weak一起修饰基本数据类型?__weak只应用于OC对象或者block指针类型,不能用来修饰int等基本数据类型。
- block修饰的对象block内部会对它进行强引用,weak修饰的普通对象block内部会根据外部捕获进来的修饰对象决定是强引用还是弱引用。
- block内部引用没有任何修饰的局部变量,会将局部变量的值直接捕获到block内部。block内部引用使用block修饰的局部变量,会将这个局部变量包装成一个结构体对象,block内部就保留引用这个结构体的指针,相当于这个结构体指针会指向这个结构体,这个结构体内部会有这个捕获进来的局部变量的具体值。
##对象类型的Aotu变量、block变量
相同点:- 当blcok在栈上时都不会对它们产生强引用。
- 当blcok拷贝到堆上时,都会通过block内部的__main_blcok_copy_0函数通过_Block_object_assgin来对他们进行retain操作。
- 当block从堆中移除时,都会通过block内部的__Block_object_dispose函数来对它们进行释放也就release操作。
- 如果是普通对象传进入的值是3,如果是__block变量。
不同点:(引用上方面)- 当blcok调用copy_0函数会对对象进行引用,如果传进来的是对象类型的,这取决于当初block内部是如何访问,如果当初内部是通过弱引用访问外部变量的,那么block的_Block_object_assgin函数对它进行的就是弱引用;如果当初内部是通过强引用来访问外部的变量的,那么_Block_object_assgin就会对它进行强引用。
- 如果是block修饰的变量,是不分强引用或者弱引用的。一旦是block修饰的变量的话,_Block_object_assgin函数产生的直接就是强引用,对它进行一次retain操作。
- weak是不存在修饰基本数据类型的。
##block的_forwarding指针- 如果在block内部对外部变量进行赋值操作,block会将这个值赋值到Block_byref_xx_0结构体内部的变量当中去。它是如何进行赋值的呢?它是通过block的Block_byref_xx_0这个指向结构体的指针,相当于利用这个指针拿到结构体里面的。它会利用这个结构体指针拿到它里面的一个forwarding指针,再通过forwarding去拿这个变量的值进行赋值操作。因为forwarding这个指针是指向它自己的,通过forwarding指针拿到自己,再通过自己拿到里面的成员变量来访问这个值。为什么会这样设计呢?block变量可能一开始的时候是存储在栈上的,当block内部访问这个变量会被拷贝到堆上,而block引用的也是堆上的变量,所以一定要保证这个变量是存储在堆上的,为了防止这个变量还是存储在栈上面,那么就可以通过forwarding指针。
- 如果block变量是存储在栈上的,那么forwarding就是指向自己。
- 一旦block复制到堆上去,它会让栈上block变量的forwarding指针指向复制到堆上的block结构体对象,而堆上的block结构体内部的__forwarding指针指向堆上的自己。
- 这样有什么好处?假设这个变量还是再栈上,block内部访问的还是栈上的变量的话,那么通过这个变量的forwarding指针拿到的就是复制到堆上的block,然后再通过堆上的block去访问这个block变量,就能保证这个变量是赋值在堆结构体的内存中。