iOS 動態調用Block

關于block 內部實現 ,大多數博客都是使用 clang -rewrite-objc main.m 來查看c++源碼:

struct __Person__init_block_impl_0 {
  struct __block_impl impl;
  struct __Person__init_block_desc_0* Desc;
  Person *self;
  __Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __Person__init_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__init_block_impl_0*, struct __Person__init_block_impl_0*);
  void (*dispose)(struct __Person__init_block_impl_0*);
} __Person__init_block_desc_0_DATA = { 0, sizeof(struct __Person__init_block_impl_0), __Person__init_block_copy_0, __Person__init_block_dispose_0};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

解析出來的c++ 源碼,其中block 由一個結構體實現。很多文章都有解析block 的,但是基本上都是說 __block_impl-> Flags 未預留字段,由于沒有實際用到,基本也不會深究,直到看到AOP庫Aspects和RunTime庫CTObjectiveCRuntimeAdditions還有JavascriptCore才發現,原來block可以用NSInvocation來動態調用。

  • 用過NSInvocation的應該知道, 它可以用來實現對指定對象的任意方法的調用
  • 使用NSInvocation調用block 流程 并沒有selector, 調用邏輯是:
    獲取block的signature->設置target為block->設置參數->invoke 并不需要想普通方法一樣需要selector
  • NSInvocation 通過NSMethodSignature 來創建

那么通過以上幾點點,使用NSInvocation我們就需要獲取block的Signature,在JavascriptCore中使用了 _Block_has_signature 和 _Block_signature來獲取Signature,但是這兩個函數又屬于私有API,而AspectsCTObjectiveCRuntimeAdditions中都是使用block 結構體中的字段來實現的,但是又發現他們定義的結構和clang -rewrite-objc main.m 解析出來的不大一樣。直到看到了Clang 7 文檔 才發現他們使用的block和這上面的一模一樣。

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

他們用到了其中的flags和signature,其中flags是一個枚舉值

enum {
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};

結合上面block發現 flags 標識 Block_descriptor_1結構體中的結構

        struct CTBlockLiteral *blockRef = (__bridge struct CTBlockLiteral *)block;
        _flags = blockRef->flags;
        _size = blockRef->descriptor->size;
        
        if (_flags & CTBlockDescriptionFlagsHasSignature) {
            void *signatureLocation = blockRef->descriptor;
            signatureLocation += sizeof(unsigned long int);
            signatureLocation += sizeof(unsigned long int);
            
            if (_flags & CTBlockDescriptionFlagsHasCopyDispose) {
                signatureLocation += sizeof(void(*)(void *dst, void *src));
                signatureLocation += sizeof(void (*)(void *src));
            }
            
            const char *signature = (*(const char **)signatureLocation);
            _blockSignature = [NSMethodSignature signatureWithObjCTypes:signature];
        }

上面為CTObjectiveCRuntimeAdditions的代碼與Aspects實現基本相同。
都是通過將block轉換為自定義與block相同結構的結構體。由于結構體中Block_descriptor_1結構不固定,所以只有通過指針從首地址進行偏移來獲取到最后的signature;
然后使用signature創建NSInvocation,然后設置Target為block,設置參數然后調用invoke。

參考文檔

clang7文檔
NSInvocation動態調用任意block
CTObjectiveCRuntimeAdditions
Aspects

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容