oc中block的本质及底层原理
-
block的本質
-
block的種類及儲存區域
-
__block的本質
-
block的循環引用
前言:
這里就不討論block的具體寫法及使用場景了,因為當你有一天想深入了解block 的底層原理時,你早已把block寫了幾十遍了。
一、block的本質:
block是帶有自動變量的匿名函數。
注:
????????局部變量? = 自動變量(棧區)+ 靜態局部變量 (全局區)
????????這里說的自動變量是指block里面捕獲的外部局部變量,當然你也可以不捕獲
二、block的種類及儲存區域:
NSGlobalBlock :存放在全局區的block
????????只有當block里面僅僅捕獲了外部的靜態局部變量、全局變量、靜態全局變量時,該block?存.? ? ?放在全局區。當然,如果block里面什么類型的外部變量都不捕獲時,它也在全局區。?
NSStackBlock : 存放在棧區的block
NSMallocBlock : 存放在堆區
? ? ? ? 在arc下,截獲了外部自動變量的block被創建出來時存放在棧區,然后如果該block被strong/copy 修飾符修飾時,系統會將該block從棧區,copy一份到堆區,并將指針指向堆區的block。
? ? ? ? 而系統默認的就是strong類型(不管是成員變量block還是局部變量block ),所以在大部分情況下解惑了外部自動變量的block都是存放在堆區,只有當你手動的將一個局部block修飾weak屬性時,它才不會被copy到堆區。
注:ios的五大內存區域:棧區、堆區、全局區、常量區、代碼區
三、__block的本質
先說一下block截獲對象的性質
這里就可以看到,只有當截獲自動變量的基本類型數據(如int)時,block只截獲其值。當然,如果你只截獲到其值,并沒有拿到它的內存地址,在block里面你就不能對它進行修改(如+1)。
需要特別注意的是:?
strong/copy 修飾的block代碼塊里面截獲對象的時候,會持有該對象,使其引用計數+1。
__block的本質
當我們截獲了自動變量的基本類型數據,又想在block里面對它進行修改時,這個時候__block就派上用場了。
看一下基本類型數據用__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; }從源碼中我們了解到,系統將帶有__block的變量轉換成一個block結構體。并且當block被copy到堆上的時候,一并把_block變量的結構體也copy一份到堆中。其中堆中block結構體的_forwarding指針指向其變量本身。
這樣,block里面就能截獲到__block變量的結構體里面的__forwarding指針,而該指針指向了堆上_block變量。這樣,block就能對該變量進行修改了。
block的循環引用
首先我們來看一段代碼
//block1,block2是self的成員變量,block3是person類的成員變量 //會造成循環引用 self.block1 = ^{NSLog(@"%@",self); };//同樣也會造成循環引用 self.block1 = ^{NSLog(@"%@",_blcok2); };//同樣也會造成循環引用 Person *person = [[Person alloc] init]; person.block3 = ^{NSLog(@"%@",person); };讓我們來看一下這個循環引用究竟是怎么產生的
看第一個,block1是self里的成員變量,那么self持有了block1,強引用,引用計數+1.然后看到block1代碼里面引用了self,上面已經說了,copy/strong(默認)修飾的block里面都會持用,強引用截獲的對象,這樣就形成了一個閉環self->block1->self。從而導致self和block1互相引用,不會被自動釋放。
再看第二個,_block2等價于self->block2,那么就形成了self->block1->self->block2,很明顯,這也是一個閉環,也就是循環引用。
再看第三個,我們就能知道第三個也形成了一個閉環person->block3->person。
所以說導致循環引用的罪魁禍首并不是self,而是要看是否產生互相強引用,self本無過,只是我們在開發過程中常出現的循環引用的閉環里都包含了self。
那么如何避免循環引用呢?
既然他們是因為互相強引用導致的,那我們就把其中一個改為弱引用就好了
__weak __typeof(self) weakSelf = self; //聲明一個弱引用指針self.block1 = ^{NSLog(@"%@",weakSelf); };這時,我們再來看一下他們之間的引用,self->block1->weakSelf。
由于此時,weakSelf是一個弱引用,當它再指向它的成員變量block1的時候已經不是強引用了,這時強引用循環就斷開了。
當然,如果我們在block里面僅僅調用了局部變量,也就不需要給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
總結
以上是生活随笔為你收集整理的oc中block的本质及底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 强光LED手电筒方案开发设计
- 下一篇: CPU的基本工作原理