Objective-C runtime 拾遺 (三)——Block冷知識

動因

上次寫代碼時須要深刻了解Block。發現Block is nothing but a struct。今天又拾一下牙慧,彙總一下資料。順便記錄幾個源碼中的發現html

值得讀的參考

最好的文檔
Clang
中文的話,這篇也夠了,講得比較細:
談Objective-C block的實現
這篇也講解得不錯:
Block技巧與底層解析segmentfault

另外跟本文無關的,這我的的Blog很不錯,不少底層知識。
mikeash數組

源碼:
Block_private.h
runtime.capp

參考代碼寫在前面

enum { // Flags from BlockLiteral
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

Block中的isa

這也是爲何Block能當作id類型的參數進行傳遞。ide

但如Clang文檔中所述:函數

The isa field is set to the address of the external _NSConcreteStackBlock, which is a block of uninitialized memory supplied in libSystem, or _NSConcreteGlobalBlock if this is a static or file level Block literal.

首先_NSConcreteStackBlock是外部的,是libSystem提供的地址,a block of uninitialized memory,實際狀況是(模擬器):ui

(lldb) p &_NSConcreteStackBlock
(void *(*)[32]) $25 = 0x00000001108dc050

(lldb) p _NSConcreteStackBlock
(void *[32]) $26 = {
  [0] = 0x00000001108dc0d0
  [1] = 0x000000010e2e6000
  [2] = 0x00007f8b88d0d100
  [3] = 0x0000000400000007
  [4] = 0x00007f8b8aa00310
  [5] = 0x0000000000000000
  [6] = 0x0000000000000000
  [7] = 0x0000000000000000
  [8] = 0x0000000000000000
  [9] = 0x0000000000000000
  [10] = 0x0000000000000000
  [11] = 0x0000000000000000
  [12] = 0x0000000000000000
  [13] = 0x0000000000000000
  [14] = 0x0000000000000000
  [15] = 0x0000000000000000
  [16] = 0x000000010de91198
  [17] = 0x000000010e2e5fd8
  [18] = 0x000000010db4cf70
  [19] = 0x0000000000000000
  [20] = 0x00007f8b8aa00350
  [21] = 0x0000000000000000
  [22] = 0x0000000000000000
  [23] = 0x0000000000000000
  [24] = 0x0000000000000000
  [25] = 0x0000000000000000
  [26] = 0x0000000000000000
  [27] = 0x0000000000000000
  [28] = 0x0000000000000000
  [29] = 0x0000000000000000
  [30] = 0x0000000000000000
  [31] = 0x0000000000000000
}

分析以後的結果是這樣的:this

  • 全部stack Blockisa都指向_NSConcreteStackBlock,runtime的時候已經初始化了。什麼時候初始化未知。rest

  • _NSConcreteStackBlock是個數組,size爲32code

  • 水平有限,沒看出規律。應該是被平分紅兩部分,第二部分從_NSConcreteStackBlock[16]開始

  • 元素都是Class(廢話),如__NSStackBlock__,__NSStackBlock

  • _NSConcreteGlobalBlock相似

  • 代碼也說明了這一點:

BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

關於flags

除了標示Block的類型,還用做reference counting:
volatile int32_t flags; // contains ref count
由於最低位被BLOCK_DEALLOCATING使用了,因此ref count每次+2

關於invoke

看代碼:

id b = ^(int n, double d, char* s){
    NSLog(@"%d %lf %s",n, d, s);
};
    
((__bridge struct Block_layout*)(b))->invoke((__bridge void *)(b),1,2.345,"hello");

官方解釋:

The invoke function pointer is set to a function that takes the Block structure as its first argument and the rest of the arguments (if any) to the Block and executes the Block compound statement.

Runtime Helper Functions

源碼的註釋這樣說的:

A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables

In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers. The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign. The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.

簡單來講,當Block引用了 1) C++ 棧上對象 2)OC對象 3) 其餘block對象 4) __block修飾的變量,並被拷貝至堆上時則須要copy/dispose輔助函數。輔助函數由編譯器生成。
除了case 1,其餘三種case都會分別調用下面的函數:

void _Block_object_assign(void *destAddr, const void *object, const int flags);
void _Block_object_dispose(const void *object, const int flags);

_Block_object_assign(或者_Block_object_dispose)會根據flags的值來決定調用相應類型的copy helper(或者dispose helper)
例如:

switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
        break;
      ...
}

上述代碼中_Block_retain_object _Block_assign以SPI的形式提供給runtime 和 CoreFoundation進行注入。

原做寫於segmentfault 連接

相關文章
相關標籤/搜索