iOS中Block實現(xiàn)原理的全面分析

Block的底層基本結(jié)構(gòu)


void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"Hello World!");
    };
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

通過clang命令查看編譯器是如何實現(xiàn)Block的蛆橡,在終端輸入clang -rewrite-objc main.m幽七,然后會在當(dāng)前目錄生成main.cpp的C++文件,代碼如下:


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

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
    }
    

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};

void blockTest()
{
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}

static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

下面我們一個一個來看

__blockTest_block_impl_0

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

__blockTest_block_impl_0Block的C++實現(xiàn)括儒,是一個結(jié)構(gòu)體,從命名可以看出表示blockTest中的第一個(0Block。通常包含兩個成員變量__block_impl impl鱼鼓,__blockTest_block_desc_0* Desc和一個構(gòu)造函數(shù)。

__block_impl

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

__block_impl也是一個結(jié)構(gòu)體

  • *isa:isa指針该编,指向一個類對象迄本,有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock课竣、_NSConcreteMallocBlock嘉赎,本例中是_NSConcreteStackBlock類型置媳。
  • Flags:block 的負載信息(引用計數(shù)和類型信息),按位存儲公条。
  • Reserved:保留變量拇囊。
  • *FuncPtr:一個指針,指向Block執(zhí)行時調(diào)用的函數(shù)靶橱,也就是Block需要執(zhí)行的代碼塊寥袭。在本例中是__blockTest_block_func_0函數(shù)。

__blockTest_block_desc_0

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};

__blockTest_block_desc_0是一個結(jié)構(gòu)體关霸,包含兩個成員變量:

  • reserved:Block版本升級所需的預(yù)留區(qū)空間传黄,在這里為0。
  • Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))队寇。

__blockTest_block_desc_0_DATA是一個__blockTest_block_desc_0的一個實例尝江。

__blockTest_block_func_0

__blockTest_block_func_0就是Block的執(zhí)行時調(diào)用的函數(shù),參數(shù)是一個__blockTest_block_impl_0類型的指針英上。

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
    }

blockTest

void blockTest()
{
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

第一部分炭序,定義Block

void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));

我們看到block變成了一個指針,指向一個通過__blockTest_block_impl_0構(gòu)造函數(shù)實例化的結(jié)構(gòu)體__blockTest_block_impl_0實例苍日,__blockTest_block_impl_0在初始化的時候需要兩個個參數(shù):

  • __blockTest_block_func_0Block塊的函數(shù)指針惭聂。
  • __blockTest_block_desc_0_DATA:作為靜態(tài)全局變量初始化__main_block_desc_0的結(jié)構(gòu)體實例指針。

第二部分相恃,調(diào)用Block

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)通過block->FuncPtr指針找到__blockTest_block_func_0函數(shù)并且轉(zhuǎn)成(void (*)(__block_impl *))類型辜纲。
((__block_impl *)block)然后將block作為參數(shù)傳給這個函數(shù)調(diào)用。

Flags

__block_impl中我們看到Flags拦耐,現(xiàn)在來詳細講一講耕腾。

在這里Block_private.h可以看到Flags的具體信息:

// Values for Block_layout->flags to describe block objects
enum {
    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
};

引用淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)的解釋:

也就是說,一般情況下杀糯,一個 block 的 flags 成員默認(rèn)設(shè)置為 0扫俺。如果當(dāng) block 需要 Block_copy()Block_release 這類拷貝輔助函數(shù),則會設(shè)置成 1 << 25 固翰,也就是 BLOCK_HAS_COPY_DISPOSE 類型狼纬。可以搜索到大量講述 Block_copy 方法的博文骂际,其中涉及到了 BLOCK_HAS_COPY_DISPOSE 疗琉。

總結(jié)一下枚舉類的用法,前 16 位即起到標(biāo)記作用歉铝,又可記錄引用計數(shù):

  • BLOCK_DEALLOCATING:釋放標(biāo)記盈简。一般常用 BLOCK_NEEDS_FREE 做 位與 操作,一同傳入 Flags ,告知該 block 可釋放柠贤。
  • BLOCK_REFCOUNT_MASK:一般參與判斷引用計數(shù)香浩,是一個可選用參數(shù)。
  • BLOCK_NEEDS_FREE:通過設(shè)置該枚舉位种吸,來告知該 block 可釋放弃衍。意在說明 block 是 heap block 呀非,即我們常說的 _NSConcreteMallocBlock 坚俗。
  • BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)(a copy helper function)。
  • BLOCK_HAS_CTOR:是否擁有 block 析構(gòu)函數(shù)(dispose function)岸裙。
  • BLOCK_IS_GC:是否啟用 GC 機制(Garbage Collection)猖败。
  • BLOCK_HAS_SIGNATURE:與 BLOCK_USE_STRET 相對,判斷是否當(dāng)前 block 擁有一個簽名降允。用于 runtime 時動態(tài)調(diào)用恩闻。

block截獲變量

截獲auto變量值

Screen Shot 2019-05-03 at 3.47.08 PM.png

我們看到直接在block修改變量會提示錯誤,為什么呢剧董?

void blockTest()
{
    int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    num = 20;
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

打印結(jié)果是10幢尚,clang改寫后的代碼如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
    }
    
    void blockTest()
{
    int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num));
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__blockTest_block_impl_0多了一個成員變量int num;,再看看構(gòu)造函數(shù)__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)翅楼,可以看到第三個參數(shù)只是變量的值尉剩,這也就解釋了為什么打印的是10,因為block截獲的是值毅臊。

使用static修飾變量

void blockTest()
{
    static int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

可以在block內(nèi)部修改變量了理茎,同時打印結(jié)果是20,30管嬉。clang改寫后的代碼如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int *num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int *num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
        (*num) = 30;
    }
    
    void blockTest()
{
    static int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, &num));
    num = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__blockTest_block_impl_0多了一個成員變量int *num;皂林,和上面不同的是,這次block截獲的是指針蚯撩,所以可以在內(nèi)部通過指針修改變量的值础倍,同時在外部修改變量的值,block也能"感知到"胎挎。那么為什么之前傳遞指針呢著隆?因為變量是棧上,作用域是函數(shù)blockTest內(nèi)呀癣,那么有可能變量比block先銷毀美浦,這時候block再通過指針去訪問變量就會有問題。而static修飾的變量不會被銷毀项栏,也就不用擔(dān)心浦辨。

全局變量

int num = 10;

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

打印結(jié)果是20,30。clang改寫后的代碼如下:

int num = 10;


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

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
        num = 30;
    }

非常簡單流酬,在初始化__blockTest_block_impl_0并沒有把num作為參數(shù)币厕,__blockTest_block_func_0中也是直接訪問全局變量。

總結(jié):

變量類型 是否捕獲到block內(nèi)部 訪問方式
局部auto變量 值傳遞
局部static變量 指針傳遞
全局變量 直接訪問

使用__block修飾變量

void blockTest()
{
    __block int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

效果和使用static修飾變量一樣芽腾,clang改寫后的代碼如下:

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num));
        (num->__forwarding->num) = 30;
    }
    
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
  void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};

void blockTest()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    (num.__forwarding->num) = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
}

哇旦装,難受啊兄dei,怎么多出來這么多東西摊滔,沒關(guān)系阴绢,慢慢分析。

__blockTest_block_impl_0多出來一個成員變量__Block_byref_num_0 *num;艰躺,我們看到經(jīng)過__block修飾的變量類型變成了結(jié)構(gòu)體__Block_byref_num_0呻袭,__blockTest_block_impl_0多出來一個成員變量__Block_byref_num_0 *num;block捕獲的是__Block_byref_num_0類型指針腺兴,

__Block_byref_num_0
我們看到__Block_byref_num_0是一個結(jié)構(gòu)體左电,并且有一個isa,因此我們可以知道它其實就是一個對象页响。同時還有一個__Block_byref_a_0 *類型的__forwardingnum篓足,num我們能猜到就是用來保存變量的值,__forwarding就有一點復(fù)雜了闰蚕,后面慢慢講栈拖。

__blockTest_block_copy_0__blockTest_block_dispose_0

__blockTest_block_copy_0中調(diào)用的是_Block_object_assign__blockTest_block_dispose_0中調(diào)用的是_Block_object_dispose陪腌。

函數(shù) 調(diào)用時機
__blockTest_block_copy_0 __block變量結(jié)構(gòu)體實例從椚杩拷貝到堆時
__blockTest_block_dispose_0 __block變量結(jié)構(gòu)體實例引用計數(shù)為0時

關(guān)于_Block_object_assign_Block_object_dispose更詳細代碼可以在runtime.c 中查看。

BLOCK_FIELD_IS_BYREF
我們看到_Block_object_assign_Block_object_dispose中都有個參數(shù)值為8诗鸭,BLOCK_FIELD_IS_BYREF類型染簇,什么意思呢?在Block_private.h 中可以查看到:

// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
  • BLOCK_FIELD_IS_OBJECT:OC對象類型
  • BLOCK_FIELD_IS_BLOCK:是一個block
  • BLOCK_FIELD_IS_BYREF:在棧上被__block修飾的變量
  • BLOCK_FIELD_IS_WEAK:被__weak修飾的變量强岸,只在Block_byref管理內(nèi)部對象內(nèi)存時使用
  • BLOCK_BYREF_CALLER:處理Block_byref內(nèi)部對象內(nèi)存的時候會加的一個額外標(biāo)記(告訴內(nèi)部實現(xiàn)不要進行retain或者copy)

__blockTest_block_desc_0
我們可以看到它多了兩個回調(diào)函數(shù)指針*copy*dispose锻弓,這兩個指針會被賦值為__main_block_copy_0__main_block_dispose_0

最后我們看到訪問num是這樣的:

__Block_byref_num_0 *num = __cself->num; // bound by ref   

(num->__forwarding->num) = 30;

下面就講一講為什么要這樣。

Block的內(nèi)存管理

在前面我們講到__block_impl指向的_NSConcreteStackBlock類型的類對象蝌箍,其實總共有三種類型:

類型 存儲區(qū)域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 數(shù)據(jù)區(qū)
_NSConcreteMallocBlock

前面也講到copydispose青灼,在ARC環(huán)境下,有哪些情況編譯器會自動將棧上的把Block從棧上復(fù)制到堆上呢妓盲?

Block從棧中復(fù)制到堆
調(diào)用Block的copy實例方法時
Block作為函數(shù)返回值返回時
在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候
將block賦給帶有__strong修飾符的id類型或者Block類型時

當(dāng)Bock從棧中復(fù)制到堆杂拨,__block也跟著變化:

Screen Shot 2019-05-04 at 1.03.23 AM.png
  1. 當(dāng)Block在棧上時,__block的存儲域是棧悯衬,__block變量被棧上的Block持有弹沽。
  2. 當(dāng)Block被復(fù)制到堆上時,會通過調(diào)用Block內(nèi)部的copy函數(shù),copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)策橘。此時__block變量的存儲域是堆炸渡,__block變量被堆上的Block持有。
  3. 當(dāng)堆上的Block被釋放丽已,會調(diào)用Block內(nèi)部的dispose蚌堵,dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose,堆上的__block被釋放沛婴。
Screen Shot 2019-05-04 at 1.09.18 AM.png
  1. 當(dāng)多個棧上的Block使用棧上的__block變量吼畏,__block變量被棧上的多個Block持有。
  2. 當(dāng)Block0被復(fù)制到堆上時瘸味,__block也會被復(fù)制到堆上宫仗,被堆上Block0持有够挂。Block1仍然持有棧上的__block旁仿,原棧上__block變量的__forwarding指向拷貝到堆上之后的__block變量。
  3. 當(dāng)Block1也被復(fù)制到堆上時孽糖,堆上的__block被堆上的Block0Block1只有枯冈,并且__block的引用計數(shù)+1。
  4. 當(dāng)堆上的Block都被釋放办悟,__block變量結(jié)構(gòu)體實例引用計數(shù)為0尘奏,調(diào)用_Block_object_dispose,堆上的__block被釋放病蛉。

下圖是描述__forwarding變化炫加。這也就能解釋__forwarding存在的意義:

__forwarding 保證在棧上或者堆上都能正確訪問對應(yīng)變量

Screen Shot 2019-05-04 at 2.52.00 PM.png
int main(int argc, char * argv[]) {

    int num = 10;

    NSLog(@"%@",[^{
        NSLog(@"%d",num);
    } class]);

    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };

    NSLog(@"%@",[block class]);
}

打印結(jié)果:

2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__

我們可以看到第一個Block沒有賦值給__strong指針,而第二個Block沒有賦值給__strong指針铺然,所以第一個在棧上俗孝,而第二個在堆上。

Block截獲對象

int main(int argc, char * argv[]) {
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        NSLog(@"%@",[^{
            NSLog(@"%@",person.name);
        } class]);
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
}

打印結(jié)果:

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation Person

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end

typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        NSLog(@"%@",[^{
            NSLog(@"%@",person.name);
        } class]);
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
}

我們看到當(dāng)Block內(nèi)部訪問了對象類型的auto對象時魄健,如果Block是在棧上赋铝,將不會對auto對象產(chǎn)生強引用。

auto Strong 對象


typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    Block block;
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        block = ^{
            NSLog(@"%@",person.name);
        };
        person.name = @"david";
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
    block ();
}

打印結(jié)果是

2019-05-04 17:46:27.083280+0800 BlockTest[33745:16864251] +++++++++++++
2019-05-04 17:46:27.083934+0800 BlockTest[33745:16864251] ------------
2019-05-04 17:46:27.084018+0800 BlockTest[33745:16864251] david
2019-05-04 17:46:27.084158+0800 BlockTest[33745:16864251] -------dealloc-------

我們看到是先打印的david再調(diào)用Person的析構(gòu)方法dealloc沽瘦,在終端輸入clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m -fobjc-arc革骨,clang在ARC環(huán)境下改寫后的代碼如下:

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

我們看到__main_block_impl_0中的Person *__strong person;成員變量。

Block截獲了auto對象析恋,當(dāng)Block被拷貝到堆上良哲,Block強引用auto對象,這就能解釋了為什么超出了person的作用域助隧,person沒有立即釋放筑凫,當(dāng)Block釋放之后,會自動去掉對該對象的強引用,該對象就會被釋放了漏健。

auto Weak 對象


typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    Block block;
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";
        __weak Person *weakPerson = person;

        block = ^{
            NSLog(@"%@",weakPerson.name);
        };
        weakPerson.name = @"david";
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
    block ();
}

打印結(jié)果是

2019-05-04 17:49:38.858554+0800 BlockTest[33856:16869229] +++++++++++++
2019-05-04 17:49:38.859218+0800 BlockTest[33856:16869229] -------dealloc-------
2019-05-04 17:49:38.859321+0800 BlockTest[33856:16869229] ------------
2019-05-04 17:49:38.859403+0800 BlockTest[33856:16869229] (null)

直接在終端輸入clang -rewrite-objc main.m會報cannot create __weak reference because the current deployment target does not support weak ref錯誤嚎货。需要用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m-fobjc-arc代表當(dāng)前是ARC環(huán)境 -fobjc-runtime=macosx-10.13:代表當(dāng)前運行時環(huán)境蔫浆,缺一不可殖属,clang之后的代碼:


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

我們看到__main_block_impl_0中的Person *__weak weakPerson;成員變量。

總結(jié):

  1. 當(dāng)Block內(nèi)部訪問了對象類型的auto對象時瓦盛,如果Block是在棧上洗显,將不會對auto對象產(chǎn)生強引用。
  2. 如果block被拷貝到堆上原环,會調(diào)用Block內(nèi)部的copy函數(shù)挠唆,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù),_Block_object_assign會根據(jù)auto對象的修飾符(__strong嘱吗,__weak玄组,__unsafe_unretained)做出相應(yīng)的操作,當(dāng)使用的是__strong時谒麦,將會對person對象的引用計數(shù)加1俄讹,當(dāng)為__weak時,引用計數(shù)不變绕德。
  3. 如果Block從對上移除患膛,會調(diào)用block內(nèi)部的dispose函數(shù),內(nèi)部會調(diào)用_Block_object_dispose函數(shù)耻蛇,這個函數(shù)會自動釋放引用的auto對象踪蹬。

Block循環(huán)引用


@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^block)(void);

- (void)testReferenceSelf;

@end

@implementation Person

- (void)testReferenceSelf {
    self.block = ^ {
        NSLog(@"self.name = %s", self.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end


int main(int argc, char * argv[]) {
    Person *person = [[Person alloc] init];
    person.name = @"roy";
    [person testReferenceSelf];
}

打印結(jié)果是self.name = royPerson的析構(gòu)方法dealloc并沒有執(zhí)行臣咖,這是典型的循環(huán)引用跃捣,下面我們研究研究為啥會循環(huán)引用。clang改寫后的代碼如下:


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

static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

我們看到本來Person中testReferenceSelf方法是沒有參數(shù)的亡哄,但是轉(zhuǎn)成C++之后多出來兩個參數(shù):* self_cmd枝缔,再看看__Person__testReferenceSelf_block_impl_0中多出來一個成員變量Person *const __strong self;,因此我們知道Person中block捕獲了self蚊惯,block強引用self愿卸,同時self也強引用block,因此形成循環(huán)引用截型。

Weak解除循環(huán)引用

@implementation Person

- (void)testReferenceSelf {
    __weak typeof(self) weakself = self;
    self.block = ^ {
        __strong typeof(self) strongself = weakself;
        NSLog(@"self.name = %s", strongself.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end

打印結(jié)果:

2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy
2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc-------

我們看到Person對象被正常釋放了趴荸,說明不存在循環(huán)引用,為什么呢宦焦?clang改寫后的代碼如下:

struct __Person__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __Person__testReferenceSelf_block_desc_0* Desc;
  Person *const __weak weakself;
  __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    __attribute__((objc_ownership(weak))) typeof(self) weakself = self;
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

可以看到__Person__testReferenceSelf_block_impl_0結(jié)構(gòu)體中weakself成員是一個__weak修飾的Person類型對象发钝,也就是說__Person__testReferenceSelf_block_impl_0對Person的依賴是弱依賴顿涣。weak修飾變量是在runtime中進行處理的,在Person對象的Dealloc方法中會調(diào)用weak引用的處理方法酝豪,從weak_table中尋找弱引用的依賴對象涛碑,進行清除處理。

最后

好了孵淘,關(guān)于Block就寫到這里了蒲障,花了五一的三天時間解決了一個基礎(chǔ)知識點,如釋重負瘫证,寫的真心累揉阎。

參考文章
淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)
Objc Block實現(xiàn)分析
(四)Block之 __block修飾符及其存儲域
(三)Block之截獲變量和對象
關(guān)于Block再啰嗦幾句
__block變量存儲域
Block學(xué)習(xí)⑤--block對對象變量的捕獲
淺談Block實現(xiàn)原理及內(nèi)存特性之三: copy過程分析
iOS底層原理總結(jié) - 探尋block的本質(zhì)(一)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市背捌,隨后出現(xiàn)的幾起案子毙籽,更是在濱河造成了極大的恐慌,老刑警劉巖毡庆,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坑赡,死亡現(xiàn)場離奇詭異,居然都是意外死亡扭仁,警方通過查閱死者的電腦和手機垮衷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門厅翔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乖坠,“玉大人,你說我怎么就攤上這事刀闷⌒鼙茫” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵甸昏,是天一觀的道長顽分。 經(jīng)常有香客問我,道長施蜜,這世上最難降的妖魔是什么卒蘸? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮翻默,結(jié)果婚禮上缸沃,老公的妹妹穿的比我還像新娘。我一直安慰自己修械,他們只是感情好趾牧,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肯污,像睡著了一般翘单。 火紅的嫁衣襯著肌膚如雪吨枉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天哄芜,我揣著相機與錄音貌亭,去河邊找鬼。 笑死认臊,一個胖子當(dāng)著我的面吹牛属提,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播美尸,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼冤议,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了师坎?” 一聲冷哼從身側(cè)響起恕酸,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胯陋,沒想到半個月后蕊温,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡遏乔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年义矛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盟萨。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡凉翻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捻激,到底是詐尸還是另有隱情制轰,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布胞谭,位于F島的核電站垃杖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丈屹。R本人自食惡果不足惜调俘,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺垒。 院中可真熱鬧彩库,春花似錦、人聲如沸袖牙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞭达。三九已至司忱,卻和暖如春皇忿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坦仍。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工鳍烁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人繁扎。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓幔荒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梳玫。 傳聞我的和親對象是個殘疾皇子爹梁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容