Objective-C Block 筆記二-Blocks的實現

Block 的實質

Block 是“帶有自動變量值的匿名函數”,我們可以通過 Block 的實現來加深理解。首先通過 clang 工具可以將含有 Block 的源代碼轉換為一般C語言代碼。
將下面代碼保存為 test.m:

int main()
{
    void (^blk)(void) = ^{int i=0;};
    
    blk();
    
    return 0;
}

在終端執行命令clang -rewrite-objc test.m,生成 test.cpp C 代碼文件,我們來看下 block 語句^{int i=0;};轉換后的代碼:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i=0; }

__main_block_impl_0 是一個結構體,如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0有兩個成員變量:impl,Desc,還有一個構造函數__main_block_impl_0(void*, struct, int)

我們先看impl__block_impl 類型, block 結構體:

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

第二個成員變量 Desc,是__main_block_desc_0類型指針:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} 

reserved 為預留字段,Block_size 是該 block 的大小

下面我們看看該結構體的構造函數

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}

以上就是初始化 __main_block_impl_0結構體成員的源代碼。fp 是 __main_block_func_0 函數指針,賦給 impl.FuncPtr,通過 impl.FuncPtr 即可調用 block 函數。__block_impl結構體的 isa 成員使用_NSConcreteStackBlock初始化,這里的_NSConcreteStackBlock是什么?首先要理解 Objective-C 類和對象的實質。其實,所謂 Block 就是 Objective-C 對象。在 /usr/include/objc/runtime.h 中 isa 的聲明:

typedef struct objc_object {
    Class isa;
} *id;

isa 保持該類的結構體實例指針。_NSConcreteStackBlock 是 Block 結構體存儲類型。

Block 存儲區

  • _NSConcreteStackBlock:存儲在棧上,超出作用域被銷毀。
  • _NSConcreteGlobalBlock:存儲在程序的數據區域中。當 block 定義在全區或者 block 沒有截獲外部變量時,block 存在數據區。
  • _NSConcreteMallocBlock:存儲在由 malloc 函數分配的堆中。使用此種方式,可在 block 作用域外訪問 block,引用計數為 0 時被銷毀。

截獲自動變量

int main()
{
    int val = 3;
    void (^blk)(void) = ^{int i = val;};
    
    blk();
    
    return 0;
}

那么 __main_block_impl_0 結構體變成了

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val;
}

多了一個成員變量 val,val 被截獲了。所謂的截獲自動變量值,就是 block 所使用的外部變量被保存到 Block 的結構體實例中。

__block

block 不能修改外部變量,但如果在聲明變量的時候加上關鍵字 __block 就可在 block 內部修改外部變量。這是如何實現的?看代碼:

int main()
{
    __block int val = 3;
    void (^blk)(void) = ^{val = 1;};
    
    blk();
    
    return 0;
}

轉換后的代碼多了一個結構體聲明

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

val 被轉換成一個結構體,其中__forwarding 是一個指向自身的指針,當 block 被拷貝時,該指針指向堆上的結構體實例。而__main_block_impl_0 val 變成對這個結構體的引用:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val;
}

因此可以在 block 內部修改外部的變量。

同時,改變的還有__main_block_desc_0,多了兩個成員變量 copy 和 dispose,在拷貝 block 的時候使用到。

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

變量作用域結束時,棧上的 __block 變量和 Block 也被廢棄,復制到堆上的 __block 變量和 Block 在變量作用域結束時不受影響。

__block用結構體成員變量__forwarding可以實現無論__block變量配置在棧上還是堆上時都能夠正確地訪問__block變量。

Block 循環引用

如果在 Block 中使用附有 __strong 修飾符的對象,那么當 Block 從棧復制到堆時,該對象為 Block 所持有,容易引起循環引用。如下代碼

typedef void (^blk_t)(void);

@interface MyObject : NSObject {
    blk_t blk_;
    id obj_;
}
@end

@implementation MyObject

- (id)init {
    self = [super init];
    
    blk_ = ^{NSLog(@"obj_ = %@", obj_);};
        
    return self;
}

@end

編譯器會產生警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle。但很多時候編譯器無法判斷是否產生循環引用,我們在使用 block 的時候要特別小心,避免循環引用可以使用 __weak 修飾符。

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

推薦閱讀更多精彩內容