oc中block的本质及底层原理
-
block的本質(zhì)
-
block的種類及儲存區(qū)域
-
__block的本質(zhì)
-
block的循環(huán)引用
前言:
這里就不討論block的具體寫法及使用場景了,因為當你有一天想深入了解block 的底層原理時,你早已把block寫了幾十遍了。
一、block的本質(zhì):
block是帶有自動變量的匿名函數(shù)。
注:
????????局部變量? = 自動變量(棧區(qū))+ 靜態(tài)局部變量 (全局區(qū))
????????這里說的自動變量是指block里面捕獲的外部局部變量,當然你也可以不捕獲
二、block的種類及儲存區(qū)域:
NSGlobalBlock :存放在全局區(qū)的block
????????只有當block里面僅僅捕獲了外部的靜態(tài)局部變量、全局變量、靜態(tài)全局變量時,該block?存.? ? ?放在全局區(qū)。當然,如果block里面什么類型的外部變量都不捕獲時,它也在全局區(qū)。?
NSStackBlock : 存放在棧區(qū)的block
NSMallocBlock : 存放在堆區(qū)
? ? ? ? 在arc下,截獲了外部自動變量的block被創(chuàng)建出來時存放在棧區(qū),然后如果該block被strong/copy 修飾符修飾時,系統(tǒng)會將該block從棧區(qū),copy一份到堆區(qū),并將指針指向堆區(qū)的block。
? ? ? ? 而系統(tǒng)默認的就是strong類型(不管是成員變量block還是局部變量block ),所以在大部分情況下解惑了外部自動變量的block都是存放在堆區(qū),只有當你手動的將一個局部block修飾weak屬性時,它才不會被copy到堆區(qū)。
注:ios的五大內(nèi)存區(qū)域:棧區(qū)、堆區(qū)、全局區(qū)、常量區(qū)、代碼區(qū)
三、__block的本質(zhì)
先說一下block截獲對象的性質(zhì)
這里就可以看到,只有當截獲自動變量的基本類型數(shù)據(jù)(如int)時,block只截獲其值。當然,如果你只截獲到其值,并沒有拿到它的內(nèi)存地址,在block里面你就不能對它進行修改(如+1)。
需要特別注意的是:?
strong/copy 修飾的block代碼塊里面截獲對象的時候,會持有該對象,使其引用計數(shù)+1。
__block的本質(zhì)
當我們截獲了自動變量的基本類型數(shù)據(jù),又想在block里面對它進行修改時,這個時候__block就派上用場了。
看一下基本類型數(shù)據(jù)用__block修飾之后的源碼
__block int i = 0;源碼: struct __Block_byref_i_0 {void *__isa; //isa指針 __Block_byref_i_0 *__forwarding; //該指針指向自身int __flags; //標記int __size; //大小int i; // 變量值 }; //新人只看上面這塊就行了struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_i_0 *i; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_i_0 *i = __cself->i; // bound by ref(i->__forwarding->i) ++;NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);return 0; }從源碼中我們了解到,系統(tǒng)將帶有__block的變量轉(zhuǎn)換成一個block結(jié)構(gòu)體。并且當block被copy到堆上的時候,一并把_block變量的結(jié)構(gòu)體也copy一份到堆中。其中堆中block結(jié)構(gòu)體的_forwarding指針指向其變量本身。
這樣,block里面就能截獲到__block變量的結(jié)構(gòu)體里面的__forwarding指針,而該指針指向了堆上_block變量。這樣,block就能對該變量進行修改了。
block的循環(huán)引用
首先我們來看一段代碼
//block1,block2是self的成員變量,block3是person類的成員變量 //會造成循環(huán)引用 self.block1 = ^{NSLog(@"%@",self); };//同樣也會造成循環(huán)引用 self.block1 = ^{NSLog(@"%@",_blcok2); };//同樣也會造成循環(huán)引用 Person *person = [[Person alloc] init]; person.block3 = ^{NSLog(@"%@",person); };讓我們來看一下這個循環(huán)引用究竟是怎么產(chǎn)生的
看第一個,block1是self里的成員變量,那么self持有了block1,強引用,引用計數(shù)+1.然后看到block1代碼里面引用了self,上面已經(jīng)說了,copy/strong(默認)修飾的block里面都會持用,強引用截獲的對象,這樣就形成了一個閉環(huán)self->block1->self。從而導致self和block1互相引用,不會被自動釋放。
再看第二個,_block2等價于self->block2,那么就形成了self->block1->self->block2,很明顯,這也是一個閉環(huán),也就是循環(huán)引用。
再看第三個,我們就能知道第三個也形成了一個閉環(huán)person->block3->person。
所以說導致循環(huán)引用的罪魁禍首并不是self,而是要看是否產(chǎn)生互相強引用,self本無過,只是我們在開發(fā)過程中常出現(xiàn)的循環(huán)引用的閉環(huán)里都包含了self。
那么如何避免循環(huán)引用呢?
既然他們是因為互相強引用導致的,那我們就把其中一個改為弱引用就好了
__weak __typeof(self) weakSelf = self; //聲明一個弱引用指針self.block1 = ^{NSLog(@"%@",weakSelf); };這時,我們再來看一下他們之間的引用,self->block1->weakSelf。
由于此時,weakSelf是一個弱引用,當它再指向它的成員變量block1的時候已經(jīng)不是強引用了,這時強引用循環(huán)就斷開了。
當然,如果我們在block里面僅僅調(diào)用了局部變量,也就不需要給self聲明弱引用指針了。
參考文章:
https://www.jianshu.com/p/ee9756f3d5f6
https://blog.csdn.net/olsQ93038o99S/article/details/83829415
https://blog.csdn.net/jingqiu880905/article/details/51997126
https://www.jianshu.com/p/53cedd7bafa4
總結(jié)
以上是生活随笔為你收集整理的oc中block的本质及底层原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 强光LED手电筒方案开发设计
- 下一篇: CPU的基本工作原理