14 - block的底層分析

OC底層原理探索文檔匯總

上文說到,block本質(zhì)是一個(gè)可以捕獲變量的匿名函數(shù)坑质,并且具有對(duì)象的特征,也可以看做是一個(gè)僅有一個(gè)函數(shù)的對(duì)象。接下來就通過Clang和查看源碼分析block的底層實(shí)現(xiàn)來證明

主要內(nèi)容:
1芹敌、block的本質(zhì)
2、__block的原理
3垮抗、block的底層類型
4氏捞、block從棧拷貝到堆的過程分析

1冒版、block的本質(zhì)

1.1 block在底層的結(jié)構(gòu)

定義block.c文件

#include "stdio.h"
int main(){
    void(^block)(void) = ^{
        printf("wy");
    };
    return 0;
}

通過Clang編譯

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

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

//******簡(jiǎn)化******
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//構(gòu)造函數(shù)

block->FuncPtr(block);//block調(diào)用執(zhí)行

說明:

  • 可以看出__main_block_impl_0用來構(gòu)造block
  • __main_block_impl_0函數(shù)傳入兩個(gè)參數(shù)液茎,__main_block_func_0和&__main_block_desc_0_DATA。
  • 通過指向block的FuncPtr方法進(jìn)行調(diào)用

查看結(jié)構(gòu)體

//**block定義結(jié)構(gòu)體**
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;
  }
};

//**block實(shí)現(xiàn)結(jié)構(gòu)體**
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

說明:

  • 可以看到__main_block_impl_0結(jié)構(gòu)體是用以定義block的一個(gè)結(jié)構(gòu)體辞嗡。包含block結(jié)構(gòu)體捆等、block描述信息,以及一個(gè)同名構(gòu)造函數(shù)
    • impl是block本身
    • Desc會(huì)提供一些定義block時(shí)的描述信息
    • 在構(gòu)造函數(shù)中會(huì)將外界的值賦值到block結(jié)構(gòu)體中续室。以此來構(gòu)造block
    • 重點(diǎn)看的就是給impl的isa賦值block類型栋烤,還有將外界的函數(shù)賦值給block。
  • 在block結(jié)構(gòu)體__block_impl中包含isa和FuncPtr挺狰,還有Flags和Reserved(這兩個(gè)不用關(guān)注)
    • 結(jié)構(gòu)體中包含有isa班缎,有三種類型,NSGlobalBlock她渴、NSMallocBlock达址、NSStackBlock,這也可以說明block可以看做一個(gè)對(duì)象趁耗,因?yàn)樗衖sa沉唠,并且指向不同的結(jié)構(gòu)體類型。
    • FuncPtr就是block中的函數(shù)苛败,它用來執(zhí)行具體的功能

1.2 block如何捕獲外界變量

定義一個(gè)變量满葛,并在block中調(diào)用

int main(){
    int a = 11;
    void(^block)(void) = ^{
        printf("WY - %d", a);
    };
    
     block();
    return 0;
}

底層編譯:

//**block實(shí)現(xiàn)結(jié)構(gòu)體**
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//block定義結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;//編譯時(shí)就自動(dòng)生成了相應(yīng)的變量
  // a(_a) 的做法是將傳入的_a賦值給當(dāng)前結(jié)構(gòu)體中的a
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;//block的isa默認(rèn)是stackBlock
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//block函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  //在函數(shù)中會(huì)自動(dòng)創(chuàng)建一個(gè)局部變量,并將外界的變量值賦值給局部變量a中
  //屬于值拷貝罢屈,也就是直接將11賦值給a.因此__cself->a和a沒有關(guān)系
  int a = __cself->a; // bound by copy 值拷貝嘀韧,即 a = 11
  printf("WY - %d", a);
}
    
int main(){

    int a = 11;
    //當(dāng)block使用外界變量時(shí),會(huì)自動(dòng)將變量傳入到block中
    void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));

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

說明:

  1. 當(dāng)block使用外界的變量時(shí)缠捌,會(huì)將變量通過block的構(gòu)造函數(shù)中傳入到block定義結(jié)構(gòu)體中
  2. 在block的構(gòu)造函數(shù)中會(huì)將變量賦值給block定義結(jié)構(gòu)體__main_block_impl_0的變量锄贷。(此時(shí)_main_block_impl_0結(jié)構(gòu)體會(huì)自動(dòng)創(chuàng)建一個(gè)相同名稱的變量,差別在于沒有前邊沒有
  3. 之后在block函數(shù)中也會(huì)自動(dòng)創(chuàng)建一個(gè)局部變量译蒂,同時(shí)將__main_block_impl_0結(jié)構(gòu)體中的變量賦值給局部變量。以此讓函數(shù)可以使用該變量谊却。

總結(jié):block在使用外界變量時(shí)柔昼,會(huì)在block定義結(jié)構(gòu)體中定義一個(gè)變量來保存外界變量的值。這樣就表現(xiàn)為捕獲炎辨。

1.3 __block修飾變量的原理

對(duì)a加一個(gè)__block捕透,然后在block中使用a

#include "stdio.h"
int main(){
    __block int a = 11;
    void(^block)(void) = ^{
        a++;
        printf("WY %d",a);
    };
    // block();
    return 0;
}

Clang編譯:

//block實(shí)現(xiàn)結(jié)構(gòu)體
struct __block_impl {
  void *isa;//可以看到包含有isa
  int Flags;
  int Reserved;
  void *FuncPtr;//函數(shù)指針
};

//__block修飾的變量結(jié)構(gòu)體
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;//這里是a的地址
 int __flags;
 int __size;
 int a;//最后可以找到結(jié)構(gòu)中的變量a
};

//定義block的底層結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;//block結(jié)構(gòu)體
  struct __main_block_desc_0* Desc;//描述符
  __Block_byref_a_0 *a; // 變量,在編譯時(shí)就會(huì)將外界的數(shù)據(jù)獲取到
    //構(gòu)造函數(shù)碴萧,傳入的fp就是block內(nèi)部的函數(shù)
  __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;//這里是棧block
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//block內(nèi)部的函數(shù)實(shí)現(xiàn)乙嘀,會(huì)傳入block本身
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    //將block的變量賦值給當(dāng)前函數(shù)的局部變量。
    //但因?yàn)榇颂幍腶是指針破喻,不是基本類型虎谢,所以指針賦值的修改是可以影響的。
    //在函數(shù)中是定義了一個(gè)變量a低缩,如果是基本數(shù)據(jù)類型嘉冒,進(jìn)行賦值曹货,那么就是局部變量咆繁,無法影響到外界的a
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    
    //之后將結(jié)構(gòu)體a的__forwarding結(jié)構(gòu)體拿到,在拿到結(jié)構(gòu)體中的變量a,之后進(jìn)行操作
    //也就是說這里操作的不是局部變量本身顶籽,而是局部變量結(jié)構(gòu)體的指針玩般。
    (a->__forwarding->a)++;
    printf("LG_Cooci - %d",(a->__forwarding->a));
}

int main(){

    //__block修飾的變量
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
    //block定義
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    //這里是調(diào)用,可以看到會(huì)將結(jié)構(gòu)體__Block_byref_a_0作為變量傳入到block中
    void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    return 0;
}

說明:

  • 可以看到外界的變量使用__block修飾后礼饱,其實(shí)在底層會(huì)將該變量設(shè)置為結(jié)構(gòu)體__Block_byref_a_0
    • __Block_byref_a_0結(jié)構(gòu)體主要包含兩個(gè)值坏为,一個(gè)是變量地址,一個(gè)是變量本身
  • block在使用變量時(shí)镊绪,也會(huì)傳入__Block_byref_a_0結(jié)構(gòu)體來使用匀伏,可以看到傳入的其實(shí)就是a的地址&a。
  • __main_block_impl_0中也會(huì)主動(dòng)創(chuàng)建一個(gè)變量block來存儲(chǔ)外界變量的地址蝴韭,通過a(_a->__forwarding)來進(jìn)行賦值
  • 最后在block內(nèi)部函數(shù)__main_block_func_0中也自動(dòng)創(chuàng)建一個(gè)__Block_byref_a_0局部變量用來存儲(chǔ)外界變量的地址够颠。
  • 在函數(shù)中使用變量時(shí),其實(shí)使用的是通過變量地址所指向的變量榄鉴。

總結(jié):

  • 外界變量會(huì)生成__Block_byref_a_0結(jié)構(gòu)體
  • 結(jié)構(gòu)體用來保存原始變量的指針和值
  • 將變量生成的結(jié)構(gòu)體對(duì)象的指針地址 傳遞給block履磨,然后在block內(nèi)部就可以對(duì)外界變量進(jìn)行操作了
  • 簡(jiǎn)單來說就是將值拷貝轉(zhuǎn)變?yōu)橹羔樋截悾跃涂梢孕薷耐饨缱兞苛恕?/li>

2庆尘、block的底層類型

查看libclosure-74源碼,通過查看_Block_copy的源碼實(shí)現(xiàn)剃诅,發(fā)現(xiàn)block在底層的真正類型是Block_layout

block真正類型
查看Block_layout類型的定義,是一個(gè)結(jié)構(gòu)體

// Block 結(jié)構(gòu)體
struct Block_layout {
    //指向表明block類型的類
    void *isa;//8字節(jié)
    //用來作標(biāo)識(shí)符的驶忌,類似于isa中的位域,按bit位表示一些block的附加信息
    volatile int32_t flags; // contains ref count 4字節(jié)
    //保留信息矛辕,可以理解預(yù)留位置,用于存儲(chǔ)block內(nèi)部變量信息
    int32_t reserved;//4字節(jié)
    //函數(shù)指針,指向具體的block實(shí)現(xiàn)的調(diào)用地址
    BlockInvokeFunction invoke;
    //block的附加信息
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

說明:

  • isa:指向表明block類型的類
  • flags:標(biāo)識(shí)符如筛,按bit位表示一些block的附加信息堡牡,使用位域的方式存儲(chǔ),其中flags的種類有以下幾種杨刨,主要重點(diǎn)關(guān)注BLOCK_HAS_COPY_DISPOSE 和 BLOCK_HAS_SIGNATURE晤柄。 BLOCK_HAS_COPY_DISPOSE 決定是否有 Block_descriptor_2。BLOCK_HAS_SIGNATURE 決定是否有 Block_descriptor_3
    • 第1 位 - BLOCK_DEALLOCATING妖胀,釋放標(biāo)記芥颈,-般常用 BLOCK_NEEDS_FREE 做 位與 操作,一同傳入 Flags 赚抡, 告知該 block 可釋放爬坑。
    • 低16位 - BLOCK_REFCOUNT_MASK,存儲(chǔ)引用計(jì)數(shù)的值;是一個(gè)可選用參數(shù)
    • 第24位 - BLOCK_NEEDS_FREE涂臣,低16是否有效的標(biāo)志盾计,程序根據(jù)它來決定是否增加或是減少引用計(jì)數(shù)位的 值;
    • 第25位 - BLOCK_HAS_COPY_DISPOSE,是否擁有拷貝輔助函數(shù)(a copy helper function);
    • 第26位 - BLOCK_IS_GC赁遗,是否擁有 block 析構(gòu)函數(shù);
    • 第27位署辉,標(biāo)志是否有垃圾回收;//OS X
    • 第28位 - BLOCK_IS_GLOBAL,標(biāo)志是否是全局block;
    • 第30位 - BLOCK_HAS_SIGNATURE岩四,與 BLOCK_USE_STRET 相對(duì)哭尝,判斷當(dāng)前 block 是否擁有一個(gè)簽名。用于 runtime 時(shí)動(dòng)態(tài)調(diào)用剖煌。
// Values for Block_layout->flags to describe block objects
enum {
    //釋放標(biāo)記材鹦,一般常用于BLOCK_BYREF_NEEDS_FREE做位與運(yùn)算,一同傳入flags耕姊,告知該block可釋放
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    //存儲(chǔ)引用引用計(jì)數(shù)的 值桶唐,是一個(gè)可選用參數(shù)
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    //低16位是否有效的標(biāo)志,程序根據(jù)它來決定是否增加或者減少引用計(jì)數(shù)位的值
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    //是否擁有拷貝輔助函數(shù)茉兰,(a copy helper function)決定block_description_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    //是否擁有block C++析構(gòu)函數(shù)
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    //標(biāo)志是否有垃圾回收尤泽,OSX
    BLOCK_IS_GC =             (1 << 27), // runtime
    //標(biāo)志是否是全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    //與BLOCK_HAS_SIGNATURE相對(duì),判斷是否當(dāng)前block擁有一個(gè)簽名邦邦,用于runtime時(shí)動(dòng)態(tài)調(diào)用
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    //是否有簽名
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    //使用有拓展安吁,決定block_description_3
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

  • reserved:保留信息,可以理解預(yù)留位置燃辖,猜測(cè)是用于存儲(chǔ)block內(nèi)部變量信息
  • invoke:是一個(gè)函數(shù)指針鬼店,指向block的執(zhí)行代碼
  • descriptor:block的附加信息,比如保留變量數(shù)黔龟、block的大小妇智、進(jìn)行copy或dispose的輔助函數(shù)指針滥玷。有三類
    • Block_descriptor_1是必選的
    • Block_descriptor_2 和 Block_descriptor_3都是可選的
    #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 布局
};

以上關(guān)于descriptor的可以從其構(gòu)造函數(shù)中體現(xiàn),其中Block_descriptor_2和Block_descriptor_3都是通過Block_descriptor_1的地址巍棱,經(jīng)過內(nèi)存平移得到的

源碼:

static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;//默認(rèn)打印
}
#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;//descriptor_1的地址
    desc += sizeof(struct Block_descriptor_1);//通過內(nèi)存平移獲取
    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;
}

4惑畴、block的拷貝過程

包括block本身從棧如何拷貝到堆中,捕獲的外界局部變量如何拷貝到堆中兩類航徙。

4.1 _Block_copy源碼分析

這里只有一次拷貝如贷,即將棧block拷貝為堆block,如果不使用外界的局部變量到踏,就只有這一層拷貝杠袱。

源碼

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 這里是核心重點(diǎn) block的拷貝操作: 棧Block -> 堆Block
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;//強(qiáng)轉(zhuǎn)為Block_layout類型對(duì)象,防止對(duì)外界造成影響
    if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要釋放
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block窝稿,直接返回
        return aBlock;
    }
    else {//為棧block 或者 堆block楣富,由于堆區(qū)需要申請(qǐng)內(nèi)存,所以只可能是棧區(qū)
        // Its a stack block.  Make a copy. 它是一個(gè)堆棧塊block伴榔,拷貝纹蝴。
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);//申請(qǐng)空間并接收
        if (!result) return NULL;
        //通過memmove內(nèi)存拷貝,將 aBlock 拷貝至result
        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;//可以直接調(diào)起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;//設(shè)置block對(duì)象類型為堆區(qū)block
        return result;
    }
}

說明:

  • 進(jìn)入_Block_copy源碼踪少,將block 從棧區(qū)拷貝至堆區(qū)
  • 如果需要釋放塘安,則直接釋放
  • 如果是globalBlock,說明不需要copy秉馏,直接返回
  • 反之耙旦,肯定是棧block脱羡,因?yàn)槌跏紕?chuàng)建的不可能是堆block萝究,堆block必須是通過棧block拷貝的
  • 此處將棧block拷貝為堆block
    • 通過malloc申請(qǐng)內(nèi)存空間用于接收block
    • 通過memmove將block拷貝至新申請(qǐng)的內(nèi)存中
    • 設(shè)置block對(duì)象的類型為堆區(qū)block,即result->isa = _NSConcreteMallocBlock

4.2 _Block_object_assign 分析

如果block使用外部的局部變量锉罐,則會(huì)開始接下來的兩層拷貝

首先需要知道外部變量的種類有哪些帆竹,下面這些其中用的最多的是BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF

// Block 捕獲的外界變量的種類
// 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
    //普通對(duì)象,即沒有其他的引用類型
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    //block類型作為變量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    //經(jīng)過__block修飾的變量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    //weak 弱引用變量
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    //返回的調(diào)用對(duì)象 - 處理block_byref內(nèi)部對(duì)象內(nèi)存會(huì)加的一個(gè)額外標(biāo)記脓规,配合flags一起使用
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

4.2.1 _Block_object_assign源碼

// __block 變量
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)) {
     
            //普通的對(duì)象類型
        case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        // objc 指針地址 weakSelf (self)
            // arc
        _Block_retain_object(object);
            // 持有
            //持有了當(dāng)前指針栽连,就在這里,強(qiáng)引用
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
            
            // block 被一個(gè) block 捕獲

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

說明:

  • 如果是普通對(duì)象侨舆,則交給系統(tǒng)arc處理秒紧,并通過*dest = object拷貝對(duì)象指針,即引用計(jì)數(shù)+1挨下,所以如果直接使用外界的對(duì)象熔恢,則該對(duì)象會(huì)引用計(jì)數(shù)+1不能釋放,這也是循環(huán)引用出現(xiàn)的條件
  • 如果是block類型的變量臭笆,則通過_Block_copy操作叙淌,將block從棧區(qū)拷貝到堆區(qū)
  • 如果是__weak或__block修飾的變量秤掌,調(diào)用_Block_byref_copy函數(shù),開始捕獲變量

4.2.2 進(jìn)入_Block_byref_copy源碼

源碼:

static struct Block_byref *_Block_byref_copy(const void *arg) {
    
    // Block_byref  結(jié)構(gòu)體鹰霍,捕獲變量的結(jié)構(gòu)體
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        //1闻鉴、先創(chuàng)建了一個(gè)需要捕獲的變量的結(jié)構(gòu)體
        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;
        
        //2、在這里將這個(gè)結(jié)構(gòu)體的指針都指向這兩個(gè)變量中茂洒,這樣他們兩個(gè)的修改都會(huì)改變這個(gè)內(nèi)存數(shù)據(jù)
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        
        copy->size = src->size;

        //如果有copy能力 孟岛,
        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
            //Block_byref_2是結(jié)構(gòu)體,__block修飾的可能是對(duì)象督勺,對(duì)象通過byref_keep保存蚀苛,在合適的時(shí)機(jī)進(jìn)行調(diào)用
            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;
            }

            //等價(jià)于 __Block_byref_id_object_copy
            (*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;
}

說明:

  • 先將傳入的對(duì)象,強(qiáng)轉(zhuǎn)為Block_byref結(jié)構(gòu)體類型對(duì)象
  • 再創(chuàng)建一個(gè)需要捕獲的變量的結(jié)構(gòu)體玷氏,并且將原來的數(shù)據(jù)拷貝到這個(gè)結(jié)構(gòu)體中
  • 最后讓src和copy兩者的forwarding都指向copy堵未,也就是他們指向同一個(gè)區(qū)域,這樣就可以做到他們兩個(gè)的修改都會(huì)改變這個(gè)內(nèi)存數(shù)據(jù)

總結(jié):

  • 最初創(chuàng)建的需要捕獲的變量的結(jié)構(gòu)體是在棧中
  • 之后這個(gè)結(jié)構(gòu)體從棧中copy到堆中
  • 最后將棧的指針指向堆中的結(jié)構(gòu)體盏触,堆中的結(jié)構(gòu)體的指針指向自己渗蟹。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赞辩,隨后出現(xiàn)的幾起案子雌芽,更是在濱河造成了極大的恐慌,老刑警劉巖辨嗽,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件世落,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡糟需,警方通過查閱死者的電腦和手機(jī)屉佳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洲押,“玉大人武花,你說我怎么就攤上這事¤菊剩” “怎么了体箕?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挑童。 經(jīng)常有香客問我累铅,道長(zhǎng),這世上最難降的妖魔是什么站叼? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任娃兽,我火速辦了婚禮,結(jié)果婚禮上大年,老公的妹妹穿的比我還像新娘换薄。我一直安慰自己玉雾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布轻要。 她就那樣靜靜地躺著复旬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冲泥。 梳的紋絲不亂的頭發(fā)上驹碍,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音凡恍,去河邊找鬼志秃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嚼酝,可吹牛的內(nèi)容都是我干的浮还。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼闽巩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼钧舌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涎跨,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤凌蔬,失蹤者是張志新(化名)和其女友劉穎畜份,沒想到半個(gè)月后秦爆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绣硝,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年叔营,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屋彪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡审编,死狀恐怖撼班,靈堂內(nèi)的尸體忽然破棺而出歧匈,到底是詐尸還是另有隱情垒酬,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布件炉,位于F島的核電站勘究,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏斟冕。R本人自食惡果不足惜口糕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磕蛇。 院中可真熱鬧景描,春花似錦十办、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至棠绘,卻和暖如春件相,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氧苍。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工夜矗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人让虐。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓紊撕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赡突。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逛揩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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

  • 前言 在IOS開發(fā)中大家對(duì)block用的非常多,一般情況下僅僅停留在會(huì)用的層面麸俘,具體的block的底層是如何實(shí)現(xiàn)的...
    冼同學(xué)閱讀 460評(píng)論 0 3
  • iOSBlock底層原理解析 目錄 Block底層解析什么是block辩稽?block編譯轉(zhuǎn)換結(jié)構(gòu)block實(shí)際結(jié)構(gòu)b...
    荒漠現(xiàn)甘泉閱讀 940評(píng)論 0 0
  • 通過clang分析block 類似這樣一段代碼,我們通過 clang 之后生成 c++ 代碼如下: 通過簡(jiǎn)化之后我...
    晨曦的簡(jiǎn)書閱讀 280評(píng)論 0 1
  • 前言 上一篇我們講完了block的基礎(chǔ)知識(shí)从媚,這一篇我們就來看看block的底層原理逞泄。話不多說,我們創(chuàng)建一個(gè)test...
    xxxxxxxx_123閱讀 231評(píng)論 0 0
  • 前言 block的類型 從一段代碼開始 看下打印信息: 首先我們可以看到拜效,block有3中類型喷众,分別為:NSGlo...
    澤澤伐木類閱讀 318評(píng)論 0 0