iOS Objective-C Block底層原理

iOS Objective-C Block底層原理

在上一篇文章中我們對Block做了簡單的介紹,下面我們通過這篇文章對Block的底層原理進行探索。

首先提出問題:

  1. Block的本質(zhì)是什么?
  2. Block為什么需要調(diào)用block()
  3. Block是如何截獲外界變量的?
  4. __block是如何實現(xiàn)的?

1. 通過Clang查看Block的底層實現(xiàn)

1.1 編譯后的代碼簡單分析

要想知道Block的底層實現(xiàn),我們首先想到的就是通過Clang編譯一下Block代碼,然后看看其內(nèi)部的實現(xiàn)。我們創(chuàng)建一個block.c的文件,內(nèi)部代碼如下:

#include "stdio.h"

int main(){
    
    void(^block)(void) = ^{
        printf("hello block");
    };
    
    block();
    return 0;
}

通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c命令,將.c文件編譯成.cpp文件,我們找到main函數(shù)進行查看,編譯后的形式如下:

int main(){
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

去除掉類型強轉(zhuǎn),可以將編譯后的代碼簡化成如下形式:

int main(){
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

    block->FuncPtr(block);
    return 0;
}

通過簡化后的代碼我們可以看出Block等于__main_block_impl_0函數(shù),該函數(shù)有兩個參數(shù),其中第一個參數(shù)__main_block_func_0就是我們在Block代碼塊中寫的代碼。其編譯后的實現(xiàn)如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("hello block");
}

1.2 __main_block_impl_0

我們在編譯后的.cpp文件內(nèi)搜索__main_block_impl_0便可找起實現(xiàn),下面我們來看看其實現(xiàn):

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是一個結(jié)構(gòu)體,在該結(jié)構(gòu)體中第一個參數(shù)是一個__block_impl類型的imp__block_impl源碼如下:

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

__block_impl內(nèi)有四個變量:

  • isa: 類似于OC對象的isa,在這個例子中指向_NSConcreteStackBlock,實際就是指向那種類型的Block,相當(dāng)于指向類
  • Flags:這里是0
  • Reserved: 保留字段
  • FuncPtrBlock代碼塊的函數(shù)指針,通過該指針調(diào)用block

1.3 Block的調(diào)用

在編譯后的代碼中我們可以看出Block的調(diào)用是通過block->FuncPtr(block)來進行的。

  • 可以看出block內(nèi)部聲明了一個__main_block_func_0的函數(shù);
  • __main_block_impl_0中傳入的第一個參數(shù)就是__main_block_func_0
  • 在其內(nèi)部用fp表示,然后賦值給implFuncPtr屬性;
  • 所以我們可以可以通過block->FuncPtr(block)來進行調(diào)用Block

通過對Clang編譯的源碼進行查看,在block內(nèi)部并不會自動調(diào)用,所以我們需要調(diào)用底層生成的函數(shù)__main_block_func_0,才能實現(xiàn)block的調(diào)用

1.4 Block捕獲外界變量

1.4.1 僅使用變量

上面我們分析了一個最簡單的Block,沒有任何的與外界交互,如果與外界交互時,我們的Block又會是什么樣呢?

這里我們同樣使用Clang去編譯一個可以捕獲外界變量的Block,實現(xiàn)代碼如下:

#include "stdio.h"

int main(){
    int a = 123;
    void(^block)(void) = ^{
        printf("hello block a = %d",a);
    };
    
    block();
    return 0;
}

編譯后的結(jié)果:

int main(){

    int a = 123;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

    printf("hello block a = %d",a);
}

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

通過以上編譯后的代碼我們可以看出,Block如果想要捕獲外界變量,就會在其內(nèi)部創(chuàng)建一個同名變量來存儲從外界捕獲的變量。并在__main_block_func_0中取出捕獲的變量,以供函數(shù)調(diào)用的時候使用。

1.4.2 修改變量 (__block)

如果我們使用__block修飾外界變量,并在Block中修改了變量是什么樣子呢?

我們修改代碼為如下,然后通過Clang去編譯:

#include "stdio.h"

int main(){
    
    __block int a = 123;
    void(^block)(void) = ^{
        a = 10;
        printf("hello block a = %d",a);
    };
    
    block();
    return 0;
}

編譯后的結(jié)果:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

int main(){

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 123};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

通過以上編譯后的代碼我們可以看到,對于__block修飾的變量在底層被編譯成了__Block_byref_a_0類型的結(jié)構(gòu)體:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

在這個結(jié)構(gòu)體中我們可以通過一個叫做__forwarding的成員變量來間接訪問我們定義的變量。

在此處生成的__main_block_impl_0結(jié)構(gòu)體中,變量a也是取的__Block_byref_a_0類型的結(jié)構(gòu)體指針。生成代碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

對于__main_block_func_0中的變量a也同樣是取的a的地址進行修改其中的值。代碼如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref

    (a->__forwarding->a) = 10;
    printf("hello block a = %d",(a->__forwarding->a));
}

綜上所述對于使用__block修飾的變量,在通過Clang編譯后又如下結(jié)論:

  1. 對于外界的變量會編譯為__Block_byref_name_0的結(jié)構(gòu)體,其中name是外界變量的名稱
  2. 結(jié)構(gòu)體內(nèi)會保存外界變量的指針
  3. 通過結(jié)構(gòu)體內(nèi)的__forwarding的成員變量來間接訪問我們定義的變量。
  4. 對于編譯后的block結(jié)構(gòu)體__main_block_impl_0內(nèi)部也會存儲一個外界變量的__Block_byref_name_0類型的結(jié)構(gòu)體指針
  5. 通過block結(jié)構(gòu)體作為參數(shù)傳遞給生成的__main_block_func_0對外界變量進行訪問。

所以此處并不是像2.1中的那樣只是創(chuàng)建了一個同名的變量那樣簡單,在這兩節(jié)中分別使用了值拷貝和指針拷貝兩種方法:

  • 值拷貝:也就是淺拷貝,只拷貝數(shù)值,且拷貝的值不可更改,指向不同的內(nèi)存空間
  • 指針拷貝:也就是深拷貝,生成的對象與原對象指向同一片內(nèi)存空間,在一處修改的時候另一處也會被修改

1.5 小結(jié)

通過上面的分析我們可以得出如下結(jié)論:

  1. Block在底層是一個結(jié)構(gòu)體,同樣也可以使用%@打印,所以也可以理解為對象
  2. Block需要調(diào)用是因為Block代碼塊在底層是一個函數(shù),要想讓其執(zhí)行,所以需要調(diào)用
  3. Block捕獲外界變量時,會自動生成一個同名屬性
  4. Block捕獲并修改外界變量時,會生成一個__Block_byref_name_0的結(jié)構(gòu)體,并通過一個叫做__forwarding的成員變量來間接訪問我們定義的變量
  5. 所以__block的原理是生成響應(yīng)的結(jié)構(gòu)體,保存原始變量的指針和值,傳遞一個指針地址給Block

2. Block底層探索

2.1 查找Block的底層實現(xiàn)

通過以上對于Clang編譯后Block的探索后我們對Block有了初步的了解,但是我們還是想知道Block在底層的真正的實現(xiàn),以及找一份開源代碼進行研究,下面我們通過匯編去尋找一下Block的底層實現(xiàn)和實現(xiàn)庫的位置。

我們創(chuàng)建一個iOS工程編寫一段Block代碼,并添加如下斷點,然后開啟匯編調(diào)試Debug->Debug Workflow->Always Show Disassembly

16061153947068.jpg

運行程序后我們發(fā)現(xiàn)一個符號symbolobjc_retainBlock這里的匯編代碼是call說明調(diào)用了這個符號,我們在這行匯編代碼處添加斷點,如下圖:

16061153523231.jpg

過掉原本的斷點,來到上面這行處,然后按住command鼠標(biāo)點擊斷點處的向下的小箭頭來到如下圖所示的匯編代碼處:

16061153729653.jpg

通過上面的圖片我們可以知道此處又繼續(xù)調(diào)用了_Block_copy,然后我們添加_Block_copy符號斷點。過掉上面的斷點來到如下圖所示的匯編處:

16061164559120.jpg

通過上面這張圖片我們可以看到_Block_copy實現(xiàn)于libsystem_blocks.dylib源碼中。

我們可在Apple Opensource中下載各個版本的libclosure源碼。這里推薦一下LGCooci老師的libclosure-74-KCBuild,可以編譯運行的libclosure,可以運行并斷點調(diào)試Block底層的libclosure-74源碼。

2.2 Block_layout

2.2.1 Block_layout源碼及分析

首先我們?nèi)炙阉?code>_Block_copy找到它的源碼如下:

_Block_copy源碼:

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

從該函數(shù)的第一行代碼中我們看到了個Block_layout,那么我們首先來看看Block_layout這個結(jié)構(gòu)體是什么,其實這就是我們block底層的真正實現(xiàn),源碼如下:

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • isa:指向block類型的類,就是那幾種block
  • flags:標(biāo)識符,是一種位域結(jié)構(gòu),按位表示block的一些信息
  • reserved:保留字段
  • invoke:函數(shù)指針,指向具體的block實現(xiàn)的調(diào)用地址
  • descriptorblock的附加信息(其實還有Block_descriptor_2Block_descriptor_3

2.2.2 flag 分析

_Block_copy函數(shù)中我們可以看到aBlock->flags & BLOCK_NEEDS_FREE,說明flagBLOCK_NEEDS_FREE相關(guān),我們跳轉(zhuǎn)到BLOCK_NEEDS_FREE找到如下枚舉代碼:

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位BLOCK_DEALLOCATING:釋放標(biāo)記,一般常用 BLOCK_NEEDS_FREE按位與操作,一同傳入 Flags,表示該block是否可以釋放。
  • 第16位BLOCK_REFCOUNT_MASK:存儲引用計數(shù)的值,是一個可選用的參數(shù)
  • 第24位BLOCK_NEEDS_FREE:低16位是否有效的標(biāo)志,程序根據(jù)它來決定是否增加或較少引用計數(shù)位的值
  • 第25位BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)a copy helper function
  • 第26位BLOCK_HAS_CTOR:是否擁有block析構(gòu)函數(shù)
  • 第27位BLOCK_IS_GC:標(biāo)志是否有垃圾回收,應(yīng)用于OS X
  • 第28位BLOCK_IS_GLOBAL:標(biāo)志是否是全局Block
  • 第29位BLOCK_USE_STRET:與30位相反,判斷當(dāng)前Block是否擁有一個簽名,用于runtime時動態(tài)調(diào)用。
  • 第30位BLOCK_HAS_SIGNATURE:與29位相反,判斷當(dāng)前Block是否擁有一個簽名,用于runtime時動態(tài)調(diào)用。
  • 第31位:BLOCK_HAS_EXTENDED_LAYOUT:標(biāo)志block是否有擴展

2.2.3 descriptor 分析

descriptorblock的附加信息,首先在``中看到的是Block_descriptor_1,我們跳轉(zhuǎn)過去可以看到如下代碼:

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;// 保留信息
    uintptr_t size;// block大小
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;//拷貝函數(shù)指針
    BlockDisposeFunction dispose;// 銷毀
};

#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 依賴于block擴展布局
};

這里的Block_descriptor_1是必選的Block_descriptor_2Block_descriptor_3不是必選的:

Block_descriptor_2需要flags是:BLOCK_HAS_COPY_DISPOSE才會存在,Block_descriptor_3需要flagsBLOCK_HAS_SIGNATUREBLOCK_HAS_EXTENDED_LAYOUT才會存在。

我們在Block_layout中只看到Block_descriptor_1那么是怎么訪問Block_descriptor_2Block_descriptor_3的呢?我們可以在其構(gòu)造方法中找到答案,就是經(jīng)過內(nèi)存平移訪問的,源碼如下:

/****************************************************************************
Accessors for block descriptor fields
*****************************************************************************/
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;
}
#endif

// Block 的描述 : copy 和 dispose 函數(shù)
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

// Block 的描述 : 簽名相關(guān)
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

2.2.4 查看Block簽名

在面試中經(jīng)常會提到Block簽名的問題,那么我們的Block簽名到底是什么呢?在上一節(jié)中我們可以看到我們的Block簽名存儲在Block_descriptor_3中,下面我們就定義一個block通過讀取內(nèi)存的方式看一看Block的簽名長什么樣。

Block 代碼:

void (^block)(void) = ^{
    NSLog(@"test_block");
};
block();
16061993231947.jpg

以下這段話很重要!!!!!

這里是緊密的根據(jù)上一節(jié)的內(nèi)容來的,因為block在底層本質(zhì)是Block_layout,其參數(shù)中isa占8字節(jié),flags占4字節(jié),reserved占4字節(jié),invoke占8字節(jié),descriptor占8字節(jié),所以我們讀取的第一個4段內(nèi)存中的第4個就是descriptor的地址,根據(jù)上一節(jié)中我們的分析,知道了Block_descriptor_3的地址是由Block_descriptor_1偏移進行讀取的,根據(jù)_Block_descriptor_3函數(shù)中的代碼,我們的Block是否需要銷毀通過BLOCK_HAS_COPY_DISPOSE進行判斷,在讀取的第一個四段內(nèi)存中的第二段0x50000000與上flags中的BLOCK_HAS_COPY_DISPOSE也就是1 << 25結(jié)果為0,所以只需要偏移Block_descriptor_1這個結(jié)構(gòu)體的內(nèi)存大小,Block_descriptor_1有兩個屬性,分別都是uintptr_t類型,uintptr_t實際就是long占8字節(jié),兩個就是16字節(jié),所以第二個四段內(nèi)存中的第三個就是Block_descriptor_3的首地址,也就是signature簽名信息的地址,是個char *類型,占8字節(jié)。打印結(jié)果為v8@?0,所以這就是我們當(dāng)前Block的簽名。

簽名信息分析:

  • v:返回值void
  • 8:占8位,也就是block本身占用的內(nèi)存空間
  • @?:block簽名
  • 0:起始位置為0

我們再來看看有參數(shù)有返回值的Block的簽名:

代碼:

NSString* (^block1)(int a, int b) = ^(int a, int b){
    return [NSString stringWithFormat:@"%d---%d", a, b];
};
    
NSString * str = block1(1,2);
    
NSLog(@"字符串的值是:%@", str);

內(nèi)存讀取結(jié)果:

16062009165973.jpg

此時的簽名變成了@"NSString"16@?0i8i12

簽名信息分析:

  • @"NSString":返回值為OCNSString
  • 16:占用16字節(jié)
  • @?:block的簽名
  • 0i8i12:起始位置為0,block,i為分隔符,8是第一個參數(shù)的起始位置也就是int a,12 是第一個參數(shù)的起始位置也就是int b

打印一下簽名

通過[NSMethodSignature signatureWithObjCTypes:"@?"]

16062016572302.jpg

通過打印我們可以看到isBlock

PS:其實我們直接po 打印也可以看到block的簽名:

16062106117043.jpg
16062106674098.jpg

結(jié)論:

block的簽名為@?

2.3 Block 三層拷貝 捕獲外界變量并修改的底層實現(xiàn)(__block)

1.4.2中我們編譯后的代碼中多了兩個函數(shù)__main_block_copy_0__main_block_dispose_0,在那一節(jié)我們并沒有詳細的分析,下面我們就通過這兩個函數(shù)來詳細的說說在底層__block是個啥。

首先我們在__main_block_copy_0函數(shù)中可以看到其在內(nèi)部調(diào)用了_Block_object_assign函數(shù),那么我們就去libclosure中搜索一下這個函數(shù):

2.3.1 _Block_object_assign

/*******************************************************

Entry points used by the compiler - the real API!


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.

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)

So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.

When  a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions.  Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor.  And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.

So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
    __block id                   128+3       (0x83)
    __block (^Block)             128+7       (0x87)
    __weak __block id            128+3+16    (0x93)
    __weak __block (^Block)      128+7+16    (0x97)
        

********************************************************/

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only.
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

根據(jù)注釋我們總結(jié)如下:

  • 在將塊復(fù)制到堆時,塊可以引用四種不同類型的需要幫助的東西。
    • 1)基于c++棧的對象
    • 2)引用Objective-C對象
    • 3)其他模塊
      1. __block變量
  • 當(dāng)blockBlock_byrefs持有對象時,它們的復(fù)制例程助手就會使用這個入口點
  • 所以這個函數(shù)并不僅僅用于__block,對于很多從棧區(qū)拷貝到堆區(qū)的操作可能都會用到此函數(shù)

這個函數(shù)有三個參數(shù):

  • void *destArg :捕獲對象的地址、
  • const void *object:捕獲對象
  • flags: flag標(biāo)志

對于這三個參數(shù)從__block處分析,我們可以從1.4.2中的如下代碼:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
  • destArg:&dst->a
  • object:src->a
  • flags:8

__main_block_impl_0和源碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

在上一遍源碼,其他的就不多說了,已經(jīng)很顯而易見了,下面我們在看看_Block_object_assign函數(shù),該函數(shù)的核心就是通過flags中的值去找出各種外界變量種類組合,種類代碼如下:

// 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.
};
  1. BLOCK_FIELD_IS_OBJECT:對象
  2. BLOCK_FIELD_IS_BLOCK:block變量
  3. BLOCK_FIELD_IS_BYREF__block 修飾的變量
  4. BLOCK_FIELD_IS_WEAK:__weak 修飾的變量
  5. BLOCK_BYREF_CALLER:處理Block_byref內(nèi)部對象內(nèi)存的時候會加的一個額外標(biāo)記,配合上面的枚舉一起使用

此處我們看看BLOCK_FIELD_IS_BYREF也就是對應(yīng)__block時在函數(shù)內(nèi)部是怎么處理的:

case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
 // copy the onstack __block container to the heap
 // Note this __weak is old GC-weak/MRC-unretained.
 // ARC-style __weak is handled by the copy helper directly.
 __block ... x;
 __weak __block ... x;
 [^{ x; } copy];
 ********/

*dest = _Block_byref_copy(object);
break;

我們可以看到,其內(nèi)部調(diào)用的是_Block_byref_copy函數(shù)

2.3.2 _Block_byref_copy

_Block_byref_copy源碼:

// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

這個函數(shù)是__block捕獲外界變量的操作內(nèi)存拷貝以及一些常規(guī)處理。

  • 這里首先出初始化一個局部變量src存儲傳入的外部變量
  • 然后判斷block的引用計數(shù)是否為0
  • 如果不為0說明不是第一次拷貝,進入另一個分支判斷是否需要釋放(free),如果需要則調(diào)用latching_incr_int函數(shù)增加引用計數(shù)
  • 如果以上都不滿足直接返回src->forwarding
  • 如果是0就說明是第一次拷貝
    • 首先創(chuàng)建一個一樣大小的Block_byref變量copy
    • copy賦一些值,這里有一處重要的操作就是通過對copyforwardingsrcforwarding同時指向copy來達到變量的指針統(tǒng)一,以達到修改變量值時,達到同時修改的目的。
      • 下面判斷該block是否需要銷毀,如果需要就進行一些賦值操作
      • 還會判斷block是否有擴展信息,如果有也會進行一些賦值操作
      • 最后調(diào)用src2->byref_keep,那么這個byref_keep是什么呢?我們進一步分析

在分析byref_keep前我們先看看latching_incr_int函數(shù),源碼如下:

latching_incr_int源碼:

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

latching_incr_int函數(shù)中的判斷就不多說了,這里說一下為啥要加2,因為記錄引用計數(shù)是在flag的第二位中,第一位是記錄block是否釋放的,所以加2想當(dāng)于第二位加1.

byref_keep:

由于byref_keep是一個BlockByrefKeepFunction函數(shù)指針類型的屬性,所以byref_keep并不是函數(shù)名,byref_keep所在的結(jié)構(gòu)體:

//__Block 修飾的結(jié)構(gòu)體
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

//__Block 修飾的結(jié)構(gòu)體 byref_keep 和 byref_destroy 函數(shù) - 來處理里面持有對象的保持和銷毀
struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

那么到這里就算斷了嗎?那肯定不是的,我們?nèi)?code>1.4.2中Clang編譯后的代碼中去尋找__Block_byref_a_0這個結(jié)構(gòu)體中看看,這里的第五個參數(shù)為123,因為這個是int類型的外部變量,我們在外部賦值的時候為123。下面我們換個字符串試試。

OC代碼:

__block NSString *block_test = [NSString stringWithFormat:@"block_test"];
void (^block)(void) = ^{
    block_test = @"block_test_block";
    NSLog(@"LG_Block - %@",block_test);
};
block();

編譯后__Block_byref_block_test_0部分

__attribute__((__blocks__(byref))) __Block_byref_block_test_0 block_test = {
(void*)0,
(__Block_byref_block_test_0 *)&block_test,
33554432, 
sizeof(__Block_byref_block_test_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"),
sel_registerName("stringWithFormat:"),
(NSString *)&__NSConstantStringImpl__var_folders_0r_7cq1c39116927bt9x0bjsbtm0000gn_T_main_0ca87c_mi_0)};

這時我們看到__Block_byref_block_test_0第五個參數(shù)為__Block_byref_id_object_copy_131,也就是對應(yīng)byref_keep的位置因為Block_byref有四個屬性,所以Block_byref_2的第一屬性就對應(yīng)著這里面的第五個參數(shù)。

我們在Clang編譯后的代碼中搜索__Block_byref_id_object_copy_131,其實現(xiàn)如下:

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

我們發(fā)現(xiàn)__Block_byref_id_object_copy_131內(nèi)部也是調(diào)用的_Block_object_assign函數(shù),但是參數(shù)確是偏移了40位的,我們知道這里是傳入的參數(shù)是捕獲的外界變量生成的結(jié)構(gòu)體,對于這次編譯生成的結(jié)構(gòu)體源碼如下:

struct __Block_byref_block_test_0 {
  void *__isa;
__Block_byref_block_test_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *block_test;
};

由以上代碼我們可以看出前面的地址和是8+8+4+4+8+8 = 40,所以偏移40位后就是block_test的地址,也就是取的外界變量的值。所以這就是將外界捕獲的變量在通過_Block_object_assign進行拷貝處理一次。也驗證了我們一開始時說_Block_object_assign并不僅僅是處理__block的。

2.3.3 _Block_copy

對于block類型的變量會調(diào)用_Block_copy函數(shù)進行處理,下面我們就看看_Block_copy函數(shù),源碼如下:

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

此處源碼我們在一開始就上過,那時只是由該函數(shù)引入了對Block底層結(jié)構(gòu)的分析,現(xiàn)在我們分析一下該方法:

  • 首先判斷是需要釋放的,也就是堆區(qū)block則調(diào)用latching_incr_int函數(shù)直接進行引用計數(shù)的增加,該函數(shù)在上面分析過,這里就不多說了
  • 然后判斷是不是全局block,如果是就直接返回
  • 最后也就是棧區(qū)block
    • 根據(jù)block的大小申請一塊堆區(qū)空間
    • 將棧區(qū)block移動到堆區(qū)申請的空間
    • invoke進行賦值
    • flags進行賦值,是否需要釋放,引用計數(shù)等
    • 調(diào)用_Block_call_copy_helper函數(shù)處理Block_descriptor_2copy動作
    • isa設(shè)置為_NSConcreteMallocBlock也就是堆block

_Block_call_copy_helper源碼:

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}

2.3.4 小結(jié)

至此我們對Block的拷貝就分析完了,總結(jié)如下:

  • 首先會調(diào)用_Block_copy函數(shù)將棧區(qū)block拷貝到堆區(qū)(一層)
  • 對于使用__block修飾的外界變量底層會生成一個__Block_byref_xxx_0的結(jié)構(gòu)體
  • 對于該結(jié)構(gòu)體首先會調(diào)用_Block_object_assign函數(shù)對齊flags判斷進入不同分支處理,這里就是BLOCK_FIELD_IS_BYREF對應(yīng)__block
  • 在分支中會調(diào)用_Block_byref_copy函數(shù),函數(shù)內(nèi)部會拷貝一個一樣大小的結(jié)構(gòu)體,并且將變量指針指向同一區(qū)域,已達到修改值時相同的目的(二層)
  • 最后會通過Block_byref_2中的byref_keep屬性記錄的函數(shù)指針內(nèi)調(diào)用_Block_object_assign函數(shù),傳入__Block_byref_xxx_0的結(jié)構(gòu)體的外界變量的值進行又一次拷貝,這個值是通過指針偏移找到的(三層)
  • 如果外界變量不是對象時,例如int則直接記錄其值,不會進行最后一次拷貝操作
  • 對于_Block_copy函數(shù)內(nèi)對block類型的拷貝:
    • 全局block不要拷貝
    • 棧區(qū)block需要拷貝到堆區(qū)
    • 堆區(qū)block增加引用計數(shù)即可

2.4 Block的釋放

在上一節(jié)中我們提到,編譯后的代碼中會多出兩個函數(shù),其中我們分析了__main_block_copy_0,還剩下一個__main_block_dispose_0,下面我們就來看看__main_block_dispose_0都做了什么?

__main_block_dispose_0源碼:

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_test, 8/*BLOCK_FIELD_IS_BYREF*/);}

__main_block_dispose_0函數(shù)我們可以看出其內(nèi)部調(diào)用了_Block_object_dispose函數(shù),所以我們就來到libclosure源碼中搜索一下這個函數(shù),源碼如下:

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

通過源碼我們可以看到它與_Block_object_assign的實現(xiàn)方式是一致的,都是通過一個switch函數(shù)來進行匹配不同的情況:

2.4.1 釋放__block 修飾的變量(BLOCK_FIELD_IS_BYREF)

當(dāng)需要釋放__block修飾的變量時會調(diào)用_Block_byref_release函數(shù),源碼實現(xiàn)如下:

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

源碼分析:

  • 首先獲取到要釋放的變量,并取消引用轉(zhuǎn)發(fā)的指針,因為在賦值的時候是與外界變量指向同一空間的
  • 判斷是否需要釋放,如果不需要就執(zhí)行完畢了
  • 如果需要釋放
    • 獲取引用計數(shù)
    • 調(diào)用latching_decr_int_should_deallocate函數(shù)判斷是否應(yīng)該釋放
      • 應(yīng)該釋放的話就判斷flags中是否有拷貝/釋放輔助函數(shù)
        • 如果有的話就獲取一個臨時變量byref2調(diào)用byref_destroy屬性保存的函數(shù)
      • 最后釋放byref

關(guān)于byref_destroy保存的函數(shù),實現(xiàn)原理與byref_keep是一致的,我們來到編譯后的eC++文件中查看,byref_destroy屬性保存的函數(shù)是__Block_byref_id_object_dispose_131,其代碼如下:

static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

通過__Block_byref_id_object_dispose_131的代碼我們可以看出,再其內(nèi)部調(diào)用的是_Block_object_dispose函數(shù),同樣也是偏移了40,如果不是對象類型,比如int是沒有保存這個函數(shù)的,原理同拷貝原理,拷貝的時候分三層拷貝,釋放的時候也就要三層釋放。

上面提到的latching_decr_int_should_deallocate函數(shù)返回當(dāng)前block是否應(yīng)該釋放,該函數(shù)的源碼如下:

latching_decr_int_should_deallocate源碼:

static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2;
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}
  • 這里通過一個while循環(huán),不斷的判斷block是否應(yīng)該釋放
  • 首先判斷flags與上BLOCK_REFCOUNT_MASK等于BLOCK_REFCOUNT_MASK,則返回false
  • 然后判斷兩個相與是否等于0,等于的話也會返回false
  • 創(chuàng)建一個新new_value = old_value - 2,并定義一個boolresult等于false
  • 判斷舊flags與上(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)等于2,則記錄result等于true
  • 最后調(diào)用OSAtomicCompareAndSwapInt函數(shù)將新舊值比對交換,如果交換成則返回result,否則進入新的循環(huán)。

2.4.2 釋放Block變量(BLOCK_FIELD_IS_BLOCK)

當(dāng)需要釋放block變量時,需要調(diào)用_Block_release函數(shù),其源碼實現(xiàn)如下:

// API entry point to release a copied Block
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

源碼分析:

  • 首先創(chuàng)建一個局部的的block變量,如果為空直接return
  • 如果block是全局的,則直接return
  • 如果block不需要釋放也直接return
  • 如果都不是則調(diào)用latching_decr_int_should_deallocate判斷是否能夠釋放,函數(shù)分析在上一節(jié)已經(jīng)分析過了
    • 如果可以釋放就調(diào)用_Block_call_dispose_helper函數(shù),獲取descriptor_2dispose存儲的是否函數(shù)進行調(diào)用
    • 然后還會調(diào)用_Block_destructInstance,這里沒有相關(guān)實現(xiàn),應(yīng)該是沒開源吧
    • 最后free局部變量

3. 總結(jié)

  1. Block是一個匿名函數(shù),也是一個對象,在底層是一個Block_layout
  2. Block需要調(diào)用是因為Block代碼塊在底層是一個函數(shù),要想讓其執(zhí)行,所以需要調(diào)用
  3. Block捕獲外界變量的時候會生成一個同名的中間變量,取獲取到的時候的值
  4. Block使用外界變量的時候會生成一個__Block_byref_xxx_0的結(jié)構(gòu)體
  5. Block的簽名是@?
  6. Block通過__block訪問外界變量的時候會有三層拷貝
    1. 首先是block從棧拷貝到堆
    2. 將修飾的對象轉(zhuǎn)話為一個結(jié)構(gòu)體,將其拷貝到堆內(nèi)存
    3. 將修飾的對象的內(nèi)存地址也進行拷貝
  7. Block的釋放相當(dāng)于拷貝的反向,拷貝的東西都需要釋放的
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

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