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是一個結構體君丁,在該結構體中第一個參數(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橡庞,相當于指向類
  • 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;
}

編譯后的結果:

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;
}

編譯后的結果:

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類型的結構體:

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

在這個結構體中我們可以通過一個叫做__forwarding的成員變量來間接訪問我們定義的變量床嫌。

在此處生成的__main_block_impl_0結構體中跨释,變量a也是取的__Block_byref_a_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;
  }
};

對于__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編譯后又如下結論:

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

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

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

1.5 小結

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

  1. Block在底層是一個結構體,同樣也可以使用%@打印函荣,所以也可以理解為對象
  2. Block需要調(diào)用是因為Block代碼塊在底層是一個函數(shù)显押,要想讓其執(zhí)行,所以需要調(diào)用
  3. Block捕獲外界變量時傻挂,會自動生成一個同名屬性
  4. Block捕獲并修改外界變量時乘碑,會生成一個__Block_byref_name_0的結構體,并通過一個叫做__forwarding的成員變量來間接訪問我們定義的變量
  5. 所以__block的原理是生成響應的結構體金拒,保存原始變量的指針和值兽肤,傳遞一個指針地址給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鼠標點擊斷點處的向下的小箭頭來到如下圖所示的匯編代碼處:

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這個結構體是什么,其實這就是我們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:標識符,是一種位域結構吁峻,按位表示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相關用含,我們跳轉(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:釋放標記矮慕,一般常用 BLOCK_NEEDS_FREE按位與操作,一同傳入 Flags啄骇,表示該block是否可以釋放痴鳄。
  • 第16位BLOCK_REFCOUNT_MASK:存儲引用計數(shù)的值,是一個可選用的參數(shù)
  • 第24位BLOCK_NEEDS_FREE:低16位是否有效的標志缸夹,程序根據(jù)它來決定是否增加或較少引用計數(shù)位的值
  • 第25位BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)a copy helper function
  • 第26位BLOCK_HAS_CTOR:是否擁有block析構函數(shù)
  • 第27位BLOCK_IS_GC:標志是否有垃圾回收痪寻,應用于OS X
  • 第28位BLOCK_IS_GLOBAL:標志是否是全局Block
  • 第29位BLOCK_USE_STRET:與30位相反螺句,判斷當前Block是否擁有一個簽名,用于runtime時動態(tài)調(diào)用橡类。
  • 第30位BLOCK_HAS_SIGNATURE:與29位相反蛇尚,判斷當前Block是否擁有一個簽名,用于runtime時動態(tài)調(diào)用顾画。
  • 第31位:BLOCK_HAS_EXTENDED_LAYOUT:標志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的呢?我們可以在其構造方法中找到答案庶诡,就是經(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 的描述 : 簽名相關
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

以下這段話很重要;浴U合拧!撩幽!!

這里是緊密的根據(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結果為0侠碧,所以只需要偏移Block_descriptor_1這個結構體的內(nèi)存大小,Block_descriptor_1有兩個屬性缠黍,分別都是uintptr_t類型弄兜,uintptr_t實際就是long占8字節(jié),兩個就是16字節(jié),所以第二個四段內(nèi)存中的第三個就是Block_descriptor_3的首地址替饿,也就是signature簽名信息的地址语泽,是個char *類型,占8字節(jié)盛垦。打印結果為v8@?0湿弦,所以這就是我們當前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)存讀取結果:

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

結論:

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ù)注釋我們總結如下:

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

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

  • void *destArg :捕獲對象的地址、
  • const void *object:捕獲對象
  • flags: flag標志

對于這三個參數(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)存的時候會加的一個額外標記牛郑,配合上面的枚舉一起使用

此處我們看看BLOCK_FIELD_IS_BYREF也就是對應__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想當于第二位加1.

byref_keep:

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

//__Block 修飾的結構體
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

//__Block 修飾的結構體 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這個結構體中看看朦乏,這里的第五個參數(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,也就是對應byref_keep的位置因為Block_byref有四個屬性刽锤,所以Block_byref_2的第一屬性就對應著這里面的第五個參數(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ù)是捕獲的外界變量生成的結構體,對于這次編譯生成的結構體源碼如下:

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底層結構的分析,現(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設置為_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 小結

至此我們對Block的拷貝就分析完了烫葬,總結如下:

  • 首先會調(diào)用_Block_copy函數(shù)將棧區(qū)block拷貝到堆區(qū)(一層)
  • 對于使用__block修飾的外界變量底層會生成一個__Block_byref_xxx_0的結構體
  • 對于該結構體首先會調(diào)用_Block_object_assign函數(shù)對齊flags判斷進入不同分支處理界弧,這里就是BLOCK_FIELD_IS_BYREF對應__block
  • 在分支中會調(diào)用_Block_byref_copy函數(shù)凡蜻,函數(shù)內(nèi)部會拷貝一個一樣大小的結構體,并且將變量指針指向同一區(qū)域垢箕,已達到修改值時相同的目的(二層)
  • 最后會通過Block_byref_2中的byref_keep屬性記錄的函數(shù)指針內(nèi)調(diào)用_Block_object_assign函數(shù)划栓,傳入__Block_byref_xxx_0的結構體的外界變量的值進行又一次拷貝,這個值是通過指針偏移找到的(三層)
  • 如果外界變量不是對象時条获,例如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)

當需要釋放__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ù)判斷是否應該釋放
      • 應該釋放的話就判斷flags中是否有拷貝/釋放輔助函數(shù)
        • 如果有的話就獲取一個臨時變量byref2調(diào)用byref_destroy屬性保存的函數(shù)
      • 最后釋放byref

關于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ù)返回當前block是否應該釋放俺孙,該函數(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是否應該釋放
  • 首先判斷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)

當需要釋放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,這里沒有相關實現(xiàn)蚕礼,應該是沒開源吧
    • 最后free局部變量

3. 總結

  1. Block是一個匿名函數(shù)烟具,也是一個對象,在底層是一個Block_layout
  2. Block需要調(diào)用是因為Block代碼塊在底層是一個函數(shù)奠蹬,要想讓其執(zhí)行朝聋,所以需要調(diào)用
  3. Block捕獲外界變量的時候會生成一個同名的中間變量,取獲取到的時候的值
  4. Block使用外界變量的時候會生成一個__Block_byref_xxx_0的結構體
  5. Block的簽名是@?
  6. Block通過__block訪問外界變量的時候會有三層拷貝
    1. 首先是block從椂谠辏拷貝到堆
    2. 將修飾的對象轉(zhuǎn)話為一個結構體冀痕,將其拷貝到堆內(nèi)存
    3. 將修飾的對象的內(nèi)存地址也進行拷貝
  7. Block的釋放相當于拷貝的反向,拷貝的東西都需要釋放的
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狸演,一起剝皮案震驚了整個濱河市言蛇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宵距,老刑警劉巖腊尚,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異满哪,居然都是意外死亡婿斥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門哨鸭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來民宿,“玉大人,你說我怎么就攤上這事兔跌】备撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵坟桅,是天一觀的道長华望。 經(jīng)常有香客問我,道長仅乓,這世上最難降的妖魔是什么赖舟? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮夸楣,結果婚禮上宾抓,老公的妹妹穿的比我還像新娘子漩。我一直安慰自己,他們只是感情好石洗,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布幢泼。 她就那樣靜靜地躺著,像睡著了一般讲衫。 火紅的嫁衣襯著肌膚如雪缕棵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天涉兽,我揣著相機與錄音招驴,去河邊找鬼。 笑死枷畏,一個胖子當著我的面吹牛别厘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拥诡,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼触趴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渴肉?” 一聲冷哼從身側響起雕蔽,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宾娜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扇售,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赠涮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芭挽。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖困乒,靈堂內(nèi)的尸體忽然破棺而出寂屏,到底是詐尸還是另有隱情,我是刑警寧澤娜搂,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布迁霎,位于F島的核電站,受9級特大地震影響百宇,放射性物質(zhì)發(fā)生泄漏考廉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一携御、第九天 我趴在偏房一處隱蔽的房頂上張望昌粤。 院中可真熱鬧既绕,春花似錦、人聲如沸涮坐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袱讹。三九已至疲扎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間廓译,已是汗流浹背评肆。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留非区,地道東北人瓜挽。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像征绸,于是被迫代替她去往敵國和親久橙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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