Block底層原理

1、Block 的本質(zhì):

Block 本質(zhì)上也是一個(gè) OC 對(duì)象,它內(nèi)部也有個(gè) isa 指針
Block 是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境(參數(shù))的 OC 對(duì)象
Block 的調(diào)用即是函數(shù)的調(diào)用

struct __block_impl {  // block 最底層的實(shí)現(xiàn)
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr; // 函數(shù)
}

struct __xxxx_block_impl_0 { // 開發(fā)者創(chuàng)建的某個(gè)block
    struct __block_impl impl;
    struct __xxxx_block_desc_0 *Desc;
    ... // 其他變量
    
    // 構(gòu)造函數(shù)
    __xxxx_block_impl_0(void *fp, struct __xxxx_block_desc_0 *desc, int flags = 0) {
        impl.isa = &_NSConcreateStackBlock;
        impl.Flats = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
}

struct __xxxx_block_desc_0 { // 開發(fā)者創(chuàng)建的某個(gè)block的表述信息
    size_t reserved; // 預(yù)留字段,不考慮 
    size_t Block_size; // block的大小
} __xxxx_block_desc_0_DATA(0, sizeof(struct __xxxx_block_impl_0)); // 聲明了變量,并計(jì)算出了block的size

Block流程:

  • 程序在編譯的時(shí)候會(huì)創(chuàng)建兩個(gè)函數(shù):
    • 封裝了block執(zhí)行邏輯的函數(shù)(比如:_block_func_0)
    • 封裝了block描述信息的函數(shù)(比如:_block_desc_0_DATA)
  • 創(chuàng)建結(jié)構(gòu)體:
    • 將函數(shù)_block_func_0的地址賦值給內(nèi)部的FuncPtr變量
    • 將變量_block_desc_0_DATA的地址復(fù)制給Desc變量
  • 執(zhí)行Block,通過結(jié)構(gòu)體內(nèi)部的impl找到函數(shù)FuncPtr,取出函數(shù)地址并執(zhí)行(impl是struct的第一個(gè)變量,所以可以直接強(qiáng)轉(zhuǎn))

2、Block 變量捕獲:

局部變量-捕獲(因?yàn)榭绾瘮?shù)執(zhí)行)
全局變量-不捕獲


  1. 局部變量,離開作用域就銷毀

    • auto 自動(dòng)變量,可以不寫,基本類型普通定義的變量都是 auto 的)
    • static 靜態(tài)變量
    • register 高速編譯器,盡量用寄存器存儲(chǔ)數(shù)據(jù),很少用
  2. 如果在類里邊,self也會(huì)被捕獲到block中,因?yàn)閟elf是局部變量

3、Block 的類型:

Block有三種類型:

  • 全局Block(NSGlobalBlock):如果這個(gè)block沒有訪問auto變量,那么這個(gè)block就位于全局區(qū)
  • 棧Block(NSStackBlock):
    • MRC下:如果這個(gè)block訪問了auto變量,那么這個(gè)block就是棧Block,對(duì)于棧Block而言,變量的作用域結(jié)束,空間被回收
    • ARC下:如果這個(gè)block訪問了auto變量且使用了weak/assign修飾,那么這個(gè)block就是棧Block
  • 堆Block(NSMallocBlock):
    • MRC下:如果這個(gè)block訪問了auto變量且手動(dòng)進(jìn)行了copy操作,那么這個(gè)block就是堆Block
    • ARC下:如果這個(gè)block訪問了auto變量且使用了copy/strong修飾,那么這個(gè)block就是堆Block

總結(jié):
GlobalBlock: 沒有訪問auto變量(GlobalBlock->copy->GlobalBlock)
StackBlock: 訪問了auto變量 (如果StackBlock調(diào)用了copy/strong就會(huì)變成MallocBlock)
MallocBlock: 訪問了auto變量 且 strong/copy 修飾

4、Block 的 copy:

在 ARC 下某些其情況下,系統(tǒng)會(huì)自動(dòng)對(duì) block 進(jìn)行 copy,
但是只有訪問了auto對(duì)象才是MallocBloc

  • block 作為函數(shù)的返回值時(shí)
  • 將 block 賦值給強(qiáng)指針 __strong 時(shí)
  • block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數(shù)時(shí)
  • block 作為 GCD API 中的方法參數(shù)時(shí)

5、Block 訪問對(duì)象類型的 auto 變量:

  • 如果這個(gè)Block是棧Block: 不會(huì)對(duì) auto變量 產(chǎn)生強(qiáng)引用
  • 如果這個(gè)Block是堆Block: 會(huì)對(duì) auto變量 產(chǎn)生強(qiáng)引用

原理:

當(dāng) Block 被拷貝到堆上時(shí),會(huì)調(diào)用 block 內(nèi)部的 copy 函數(shù),內(nèi)部函數(shù)會(huì)根據(jù)變量的操作符(__strong、__weak、__unsfe_unretained)做出相應(yīng)的操作,類似于 retain(形成強(qiáng)引用、若引用)
當(dāng) Block 從堆上移除時(shí),會(huì)調(diào)用 block 內(nèi)部的 Dispose 函數(shù),內(nèi)部函數(shù)會(huì)自動(dòng)釋放引用的 auto 變量,類似于 release

6、Block 訪問 __block 修飾的基礎(chǔ)類型:

修改變量方式:static修飾變量、 全局變量、__block修飾變量

  • __block 可以用于解決 block 內(nèi)部無法修改 auto 變量值的問題
  • __block 不能修飾全局變量、靜態(tài)變量(static)
  • 編譯器會(huì)將 __block 變量包裝成一個(gè)對(duì)象

底層實(shí)現(xiàn):

如果變量使用了 __block 修飾,那么在編譯的時(shí)候,編譯器會(huì)將這個(gè)變量封裝成在一個(gè)結(jié)構(gòu)體中(結(jié)構(gòu)體中有isa指針,所以可以理解成對(duì)象),

struct __Block_byref_age_0 {      
    void *__isa;             
    __Block_byref_age_0 *__forwarding; // 指向__Block_byref_b_0自己本身
    int __flags;
    int __size;
    int age; // 外部的變量
};

而在 Block 的實(shí)現(xiàn)中會(huì)生成這個(gè)結(jié)構(gòu)體的指針,并進(jìn)行內(nèi)存管理,類似于auto類型對(duì)象,

訪問:age -> __forwarding -> age = 20;

其實(shí)根據(jù)讀取age的內(nèi)存地址可以發(fā)現(xiàn),不管是在 Block內(nèi)部,還是在 Block外部,訪問的age都是生成的結(jié)構(gòu)體中的age變量

為什么要使用 __forwarding 指針?
__forwarding 指針指向生成的結(jié)構(gòu)體本身,通過這個(gè)指針來訪問內(nèi)部的變量,能保證在當(dāng)Block被拷貝到堆上后,任何情況下都是訪問的堆上的變量

  • 當(dāng) Block 在棧上,使用棧Block 的 __forwarding 指針訪問的結(jié)構(gòu)體內(nèi)部的變量
  • 當(dāng) Block 被拷貝到在堆上時(shí),棧上的Block中的 __forwarding 指針指向堆Block,堆上的Block中的 __forwarding 指針指向自己本身,這時(shí)可以保證不管是通過棧Block還是堆Block訪問的內(nèi)部變量,都是堆Block的內(nèi)部變量


7、Block 內(nèi)存管理:

Block會(huì)對(duì) 對(duì)象類型變量和__block 修飾的變量進(jìn)行內(nèi)存管理
__block 修飾的都會(huì)在編譯時(shí)生成一個(gè)結(jié)構(gòu)體進(jìn)行包裝

  • 當(dāng) Block 在棧上的時(shí)候,并不會(huì)對(duì)它們產(chǎn)生強(qiáng)引用 (block 本身隨時(shí)會(huì)釋放)
  • 當(dāng) Block 被 copy 到堆上,都會(huì)通過 Copy 函數(shù),Copy 內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)來處理它們
    • a) __block 基礎(chǔ)變量,Block直接對(duì)生成的結(jié)構(gòu)體進(jìn)行強(qiáng)引用
    • b) 對(duì)象類型,Block 根據(jù)對(duì)象的修飾符(__strong、__weak、__unsfe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或弱引用(僅限于ARC時(shí)會(huì)retain,MRC下不會(huì))
    • c) __block 對(duì)象類型,Block直接對(duì)生成的結(jié)構(gòu)體進(jìn)行強(qiáng)引用,結(jié)構(gòu)體內(nèi)部根據(jù)對(duì)象的修飾符(__strong、__weak、__unsfe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或弱引用(僅限于ARC時(shí)會(huì)retain,MRC下不會(huì))
  • 當(dāng) Block 從堆上移除,就會(huì)調(diào)用 block 內(nèi)部 Dispose 函數(shù),Dispose 內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)來釋放它們

8、Block 解決循環(huán)引用:

1. ARC 下使用 __weak, __unsafe_unretained, __block

  • __weak 不會(huì)產(chǎn)生強(qiáng)引用,如果內(nèi)存釋放,指針會(huì)自動(dòng)置為 nil(推薦使用)
  • __unsafe_unretained 不會(huì)產(chǎn)生強(qiáng)引用,不安全,如果內(nèi)存釋放,指針會(huì)變成野指針
    另:
  • 使用 __block 也解決循環(huán)引用,但是必須調(diào)用 block,并且在 block 中必須將變量置 nil

2. MRC 下使用 __unsafe_unretained, __block (MRC 下不支持 __weak)

  • __unsafe_unretained 不會(huì)產(chǎn)生強(qiáng)引用,不安全,如果內(nèi)存釋放,指針會(huì)變成野指針
  • __block 它在 MRC 下不會(huì)強(qiáng)引用(推薦使用)

面試題:

1、 block 的原理是怎么樣的?本質(zhì)是什么?

封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的 OC 對(duì)象
Block 的調(diào)用即是函數(shù)的調(diào)用

2、 __block 的作用是什么?有什么使用注意點(diǎn)?

__block 可以解決 block 內(nèi)部無法修改 auto 變量值的問題  
注意點(diǎn):__block 內(nèi)存管理

3、 block 的屬性修飾詞為什么是 copy?使用 block 有哪些使用注意點(diǎn)?

block 一旦沒有 copy,就會(huì)不在堆上,在堆上方便進(jìn)行內(nèi)存管理  
使用注意:循環(huán)引用

4、 block 在修改 NSMutableArray,需不需要添加__block?

不需要

5、 適應(yīng)strong和copy修飾block有什么區(qū)別?

沒區(qū)別,不管是strong還是copy,在ARC下都會(huì)將block拷貝到堆上
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。