淺析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其實(shí)也是我們OC中的對(duì)象。通過(guò)編譯器的處理成C++底層的代碼時(shí),Block就是一個(gè)結(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中有無(wú) copy , dispose方法,有無(wú)方法簽名字符 Signature 等...)
            int Reserved;
            void *FuncPtr; //實(shí)現(xiàn)block的功能函數(shù)
    } impl ;

    
    struct __main_block_desc_0  {

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

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

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

     /**  以下都是block捕獲的變量 ,變量順序和是否捕獲進(jìn)來(lái)根據(jù)block的定義來(lái)決定 ,這里只是簡(jiǎn)單舉例**/
    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)變量是直接通過(guò)變量的地址訪問的不需要捕獲進(jìn)來(lái)*/ 
}

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

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

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

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

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

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

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

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

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

......

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

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

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

首先根據(jù)上面分析的Block內(nèi)部定義一個(gè)結(jié)構(gòu)體 ,方便我們對(duì)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;

然后定義一個(gè)簡(jiǎn)單的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);
};

下面開始對(duì)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 為 @?) 對(duì)應(yīng)的 NSInvocation 第一個(gè)參數(shù)為 block本身
        // SEL(type encodeings 為 :) 對(duì)應(yīng)的 NSInvocation 第一個(gè)參數(shù)為 selector 的 調(diào)用者(targat  type encodeings 為 @) ,第二個(gè)參數(shù)這是 _cmd (方法本身類型為SEL)
        [invocation setArgument:&param1 atIndex:1];
        [invocation setArgument:&param2 atIndex:2];
        
        [invocation invoke];
    }

看下打印 ,成功地利用NSInvocation對(duì)象調(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)其實(shí)并沒有改變目標(biāo)變量的實(shí)際內(nèi)存的數(shù)據(jù),類型強(qiáng)轉(zhuǎn)其實(shí)就是告訴編譯器 目標(biāo)變量 是我強(qiáng)轉(zhuǎn)的類型數(shù)據(jù),你對(duì)這個(gè)變量訪問時(shí)按照我指定的變量類型來(lái)訪問即可。

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

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

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

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

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