淺析Block的內(nèi)部結(jié)構(gòu) 及其 如何利用 NSInvocation 進(jìn)行調(diào)用

Block的底層內(nèi)部結(jié)構(gòu)圖

1194012-1739b7e85e46b4db.png

Block的結(jié)構(gòu)中首地址指向的就是isa指針,因此Blcok其實也是我們OC中的對象。通過編譯器的處理成C++底層的代碼時,Block就是一個結(jié)構(gòu)體,其代碼結(jié)構(gòu)如下

struct __main_block_impl_0 {

    // impl結(jié)構(gòu)體   
    struct __block_impl {
            void *isa;  //block的isa指針
            int Flags; //位移枚舉標(biāo)記(標(biāo)記desc中有無 copy , dispose方法,有無方法簽名字符 Signature 等...)
            int Reserved;
            void *FuncPtr; //實現(xiàn)block的功能函數(shù)
    } impl ;

    
    struct __main_block_desc_0  {

           size_t reserved;
          size_t Block_size; //block 的 內(nèi)存大小

          /** 以下兩個函數(shù)是在 isa 指針指向 _NSConcreteMallocBlock時才會有 **/
          void (*copy)(void); 
          void (*dispose)(void);

            /** 以下字符串是在impl.flag 包含((1 << 30)這個值是才有的變量),對應(yīng)oc中的方法簽名NSMethodSignature**/
          const char *signatureStr; 
    } * Desc;

     /**  以下都是block捕獲的變量 ,變量順序和是否捕獲進(jìn)來根據(jù)block的定義來決定 ,這里只是簡單舉例**/
    struct __Block_byref_var_0 *var ; // __block變量
    TestClass *__strong strongTestVar ; // strong 變量
    TestClass *__weak weakTestVar ; // weak 變量
    int a ; //局部普通數(shù)據(jù)類型
    int *b ;//局部靜態(tài)變量
    /**全局靜態(tài)變量是直接通過變量的地址訪問的不需要捕獲進(jìn)來*/ 
}

isa - Block 的類型(isa指針的指向)分為 3種

  1. _NSConcreteStackBlock: 只用到外部局部變量 , 且沒有強(qiáng)指針引用的block , 其實質(zhì)上就是函數(shù)棧上的局部變量,在當(dāng)前函數(shù)調(diào)用完后:(恢復(fù)棧空間的時候),就會被釋放掉。
  2. _NSConcreteGlobalBlock: 完全沒有用到外部變量 ,或只用到全局變量、靜態(tài)變量的block ,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。

Block 訪問全局變量 或 靜態(tài)變量 都是通過捕獲他們的地址進(jìn)行內(nèi)容訪問的,因為這些變量從定義的那一刻開始就確定了其地址,因此可以通過指針傳遞來捕獲到block內(nèi)部進(jìn)行訪問。而捕獲普通局部變量就不一樣,局部變量在函數(shù)返回后其內(nèi)存有可能會被會回收掉,所以是不能通過捕獲局部變量的地址到block訪問的,而是通過值傳遞來傳進(jìn)block內(nèi)部

  1. _NSConcreteMallocBlock (估計是我們最常解除的block類型了) :特點是有強(qiáng)指針引用,或者被帶有copy修飾的屬性引用,或者作為函數(shù)返回值返回時。

Flags : 這是一個位移枚舉的變量,標(biāo)記著block的一些屬性,比如

  • 結(jié)構(gòu)體的Desc中有無 copydispose函數(shù) (1 << 25)
  • 結(jié)構(gòu)體的Desc中有無 signatureStr type encodings (char * 類型字符串) (1<<30)
  • ......

FuncPtr : 就是你定義block的內(nèi)部邏輯實現(xiàn)函數(shù)的指針。通過編譯器把OC的代碼處理成c語言的函數(shù)后在block初始化時,用這個變量記錄函數(shù)的指針地址,當(dāng)block被調(diào)用時就是執(zhí)行這個函數(shù)指針指向的函數(shù)。

Desc. Block_size : block的內(nèi)存占用空間的大小

Desc -> copy + dispose 函數(shù) :block用于管理自身內(nèi)存的函數(shù)

......

更詳細(xì)的 block底層源碼實現(xiàn) 以及 __block變量的原理 推薦閱讀 深入研究Block捕獲外部變量和__block實現(xiàn)原理 這篇文章

清楚了Block的內(nèi)部結(jié)構(gòu)后,我們來看下如何理由 NSInvocation進(jìn)行調(diào)
用。

NSInvocation是一個OC中用來封裝消息發(fā)送的類,在Runtime的消息轉(zhuǎn)發(fā)的最后一個轉(zhuǎn)發(fā)步驟(Normal Forwarding)也有出現(xiàn) NSInvocation
Normal Forwarding 首先調(diào)用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector這個方法,向調(diào)用者返回一個selector 對應(yīng)的方法簽名類 NSMethodSignature對象,如果沒有返回NSMethodSignature這個類對象的話 就會拋出找不到方法的錯誤,否則,就會利用返回的 NSMethodSignature對象 生成一個NSInvocation對象傳進(jìn)- (void)forwardInvocation:(NSInvocation *)anInvocation 方法中完成消息轉(zhuǎn)發(fā)機(jī)制的最后一步。

首先根據(jù)上面分析的Block內(nèi)部定義一個結(jié)構(gòu)體 ,方便我們對block進(jìn)行內(nèi)部訪問。

struct BlockLayout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);     // (1<<25)
        void (*dispose)(void *src);
        const char *signature;                         // (1<<30)
    } *descriptor;
    // 捕獲的變量
};

enum {
    DescFlagsHasCopyDispose = (1 << 25),
    DescFlagsIsGlobal = (1 << 28),
    DescFlagsHasSignature = (1 << 30)
};
typedef int BlockDescFlags;

然后定義一個簡單的block

void(^testBlock)(int a , int b) = ^(int a , int b){
         NSLog(@"成功調(diào)用了 block");
         NSLog(@"參數(shù)1 -> a = %d , 參數(shù)2 -> b = %d" , a , b);
};

下面開始對Block內(nèi)部進(jìn)行訪問,獲取去signature(const char * )后生成NSInvoction并傳參調(diào)用。

//強(qiáng)轉(zhuǎn)為自定義的block結(jié)構(gòu)體指針
    struct BlockLayout * blockLayoutPointer =  (__bridge struct BlockLayout *)testBlock;
    int flags = blockLayoutPointer -> flags;
    
    if (flags & BlockDescFlagsHasSignature) { //有signature字符串
        
        void * signaturePoint = blockLayoutPointer -> descriptor;
        signaturePoint += sizeof(unsigned long int); //reserved
        signaturePoint += sizeof(unsigned long int); //size
        if (flags & BlockDescFlagsHasCopyDispose) {
             signaturePoint += sizeof(void (*)(void *dst , void *src)); //copy
             signaturePoint += sizeof(void (*)(void *src)); //dispose
        }
        
        //拿到 signature 字符串內(nèi)容
        const char * signatureStr = (* (const char **) signaturePoint);
        NSMethodSignature * blockSignature = [NSMethodSignature signatureWithObjCTypes:signatureStr];
        NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:blockSignature];
        invocation.target = testBlock;
        
        
        //將要傳緊block的參數(shù)
        int param1 = 10 ;
        int param2 = 20 ;
        
        // block(type encodeings 為 @?) 對應(yīng)的 NSInvocation 第一個參數(shù)為 block本身
        // SEL(type encodeings 為 :) 對應(yīng)的 NSInvocation 第一個參數(shù)為 selector 的 調(diào)用者(targat  type encodeings 為 @) ,第二個參數(shù)這是 _cmd (方法本身類型為SEL)
        [invocation setArgument:&param1 atIndex:1];
        [invocation setArgument:&param2 atIndex:2];
        
        [invocation invoke];
    }

看下打印 ,成功地利用NSInvocation對象調(diào)用了 Block。

2018-09-03 15:10:55.182181+0800 BlockWithNSInvocation[10694:872743] 成功調(diào)用了 block
2018-09-03 15:10:55.182430+0800 BlockWithNSInvocation[10694:872743] 參數(shù)1 -> a = 10 , 參數(shù)2 -> b = 20
Program ended with exit code: 0

PS:類型強(qiáng)轉(zhuǎn)其實并沒有改變目標(biāo)變量的實際內(nèi)存的數(shù)據(jù),類型強(qiáng)轉(zhuǎn)其實就是告訴編譯器 目標(biāo)變量 是我強(qiáng)轉(zhuǎn)的類型數(shù)據(jù),你對這個變量訪問時按照我指定的變量類型來訪問即可。

80e8b56c-2c3c-4a30-a9b0-d4096893ebf0.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS之武功秘籍 文章匯總[http://m.itdecent.cn/p/07991e5b1c30] 寫在前...
    長茳閱讀 717評論 0 4
  • 如下代碼就是個block,block不會主動調(diào)用 運行之后,發(fā)現(xiàn)并沒有打印。如果在block后面加個(),發(fā)現(xiàn)bl...
    Imkata閱讀 600評論 0 0
  • 1. Block的底層結(jié)構(gòu) 以下是一個沒有參數(shù)和返回值的最簡單的Block: 為了探索Block的底層結(jié)構(gòu),需要將...
    再好一點點閱讀 552評論 0 4
  • 1.weak和assign區(qū)別 修飾變量類型的區(qū)別: weak 只可以修飾對象。如果修飾基本數(shù)據(jù)類型,編譯器會報錯...
    coderjon閱讀 1,125評論 0 1
  • 一、內(nèi)存管理 1. 引用計數(shù) OC類中實現(xiàn)了引用計數(shù)器,對象知道自己當(dāng)前被引用的次數(shù)。 對象初始化時計數(shù)器為1,每...
    邢羅康閱讀 1,593評論 0 3

友情鏈接更多精彩內(nèi)容