015-iOS底層原理-block

引言-“毒雞湯”

一個好的iOS開發(fā),block必不可少戳葵,都會用就乓,但是block的底層原理,我們確都是“淺嘗輒止”拱烁,滿足開發(fā)就好生蚁。人們總說“俗話說”,但是很多俗話說戏自,不都是前人一步一個坑結(jié)結(jié)實實的踩了邦投,才總結(jié)出來的一句話。那么俗話說“逆水行舟擅笔,不進則退”志衣,是否也可以用在開發(fā)上?我覺得可以猛们。薪水可以變念脯,但是時間不等人。技術(shù)深度(廣度)應(yīng)該是至少跑贏時間(可能大盤看多了弯淘,收益跑贏大盤這句話就常掛嘴邊)绿店。
希望每一位開發(fā)者,都應(yīng)該具備“危機意識”,在有限的時間假勿,學(xué)到更多的技術(shù)借嗽。
話不多說,今天這個文章转培,是探索block的底層原理恶导。

本文Demo

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

【1】普通block

按照我們探索類的內(nèi)存時堡距,用到的探索方式兆蕉,首先查看編譯成底層c++的代碼是怎么樣的虎韵。 在main.m中設(shè)計代碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 1001;
        NSString *str = @"百事可樂";
        void (^qlBlock)(void) = ^{
            NSLog(@"a = %d -- str = %@",a,str);
        };
        
        qlBlock();
    }
    return 0;
}

打開終端,通過xcrun指令將main.m轉(zhuǎn)成main.cpp文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打開main.cpp驶社,拉到最底下亡电,找到main函數(shù)硅瞧。將括號內(nèi)的類型轉(zhuǎn)換刪除,得到精簡代碼如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 1001;
        NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_2abb7f_mi_0;
        // __main_block_impl_0函數(shù)
        void (*qlBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a, str, 570425344);

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

1或辖、void (*qlBlock)(void)賦值為一個函數(shù)調(diào)用__main_block_impl_0 ()
2枣接、搜索__main_block_impl_0找到block的底層實際上也是一個結(jié)構(gòu)體颂暇,并且__main_block_impl_0 ()block的構(gòu)造函數(shù),源碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; // 將block外部的變量a捕獲成為自己的成員變量
  NSString *str; // 將block外部的變量str捕獲成為自己的成員變量
  // 構(gòu)造函數(shù):
  // 參數(shù)1但惶、void *fp:傳的是函數(shù)__main_block_func_0()耳鸯,該函數(shù)就是block的執(zhí)行代碼塊 ^{ },這個fp傳給了 FuncPtr膀曾,在qlBlock->FuncPtr(qlBlock);中調(diào)用
  // 參數(shù)2片拍、struct __main_block_desc_0 *desc:block的信息結(jié)構(gòu)體
  // 參數(shù)3、4 外部捕獲的變量妓肢,a(_a), str(_str) 分別表示_a賦值給a捌省,_str賦值給str
  // 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *_str, int flags=0) : a(_a), str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3、qlBlock->FuncPtr(qlBlock);調(diào)起block碉钠,FuncPtr已經(jīng)在前面的構(gòu)造函數(shù)中賦值為__main_block_func_0 ()函數(shù)纲缓。因此卷拘,此處調(diào)的就是這個函數(shù),并把block自己傳進去祝高。該block已捕獲了外部變量a栗弟,str

【2】外部變量加上 __block關(guān)鍵字

我們將外部變量a和str分別加上__block關(guān)鍵字工闺,并在block執(zhí)行塊中修改a和str乍赫,main.m代碼設(shè)計如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 1001;
        __block NSString *str = @"百事可樂";
        void (^qlBlock)(void) = ^{
            a = 2002;
            str = @"可口可樂";
            NSLog(@"a = %d -- str = %@",a,str);
        };
        qlBlock();
    }
    return 0;
}

打開終端,通過xcrun指令將main.m轉(zhuǎn)成main.cpp文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

打開main.cpp改鲫,拉到最底下,找到main函數(shù)缕题。將括號內(nèi)的類型轉(zhuǎn)換刪除,得到精簡代碼如下:

1瓶摆、block的結(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) { // 將 _a-> __forwarding賦值給a
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
2、main函數(shù)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
        // __main_block_impl_0()
        void (*qlBlock)(void) = (__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
        // 調(diào)用
        qlBlock->FuncPtr)(qlBlock);
    }
    return 0;
}
3、生成的a結(jié)構(gòu)體
struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};
4荐吉、block執(zhí)行的代碼塊
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// 傳入block自己
    // 獲取block生成的并賦值好的a
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    // 結(jié)構(gòu)體a通過__forwarding獲取結(jié)構(gòu)體a內(nèi)部的成員變量a,并進行修改
    (a->__forwarding->a) = 2002;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_wv_3h_d7hqj7zz300r8bmzy6_y00000gn_T_main_6d01e1_mi_0,(a->__forwarding->a));
}

由編譯后的源碼可知悦穿,
1、外部變量加了__block后瞬沦,block對外部變量的捕獲,不再是單純的生成與之對應(yīng)的成員變量绣的,而是在內(nèi)部生成__Block_byref_a_0 *a的結(jié)構(gòu)體指針變量。
2、在main函數(shù)中踢故,先將外部的__block int a編譯后,生成一個結(jié)構(gòu)體__Block_byref_a_0(其內(nèi)部如上3淋纲、生成的a結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)部生成一個對應(yīng)的int a伙窃,用于接收外部int a的初始值1001,同時將外部int a的地址&a賦值給__forwarding)鳍怨,初始化賦值代碼:__Block_byref_a_0 a = {0,&a, 0, sizeof(__Block_byref_a_0), 1001};
3窿冯、到此為止醒串,生成的結(jié)構(gòu)體a的__forwarding為外部int a的地址,
block結(jié)構(gòu)體內(nèi)部的*a與外部變量a所指向的內(nèi)存空間是同一個缠沈,捕獲的外部變量會隨著block生成的對應(yīng)成員變量改變而改變。

block 底層結(jié)構(gòu)小結(jié)

1柬赐、block的本質(zhì)是一個結(jié)構(gòu)體;
2酝陈、block的定義沉帮,是通過block的結(jié)構(gòu)體內(nèi)部的構(gòu)造函數(shù),對block內(nèi)部的impl、desc進行賦值茄蚯。
3渗常、結(jié)構(gòu)體impl內(nèi)部有4個成員變量:isa、Flags、Reserved癌椿、FuncPtr缩功,在block構(gòu)造函數(shù)賦值時,將isa賦值為默認的NSConcreteStackBlock地址(此為編譯階段势木,運行時這個isa會賦值成真實類型)胰蝠,F(xiàn)uncPtr 的賦值是定義的block執(zhí)行代碼塊躲庄。
4噪窘、block捕獲外部變量,是在block內(nèi)部生成對應(yīng)的成員變量浩习,并通過構(gòu)造函數(shù)將捕獲的變量賦值給內(nèi)部生成的成員變量--(值拷貝)洽蛀。
5、block捕獲的外部變量驮审,如果用__block修飾,則不再是將值傳給block內(nèi)部生成的變量峡竣,而是將外部變量的地址傳給內(nèi)部成員變量荠列,達到你動我動的效果--(指針拷貝)费就。

block 源碼

1固额、在蘋果官網(wǎng)Source Browser下載libclosure-79逝慧,解壓打開Blocks工程。
2沈堡、在原來的objc工程main.m中窿给,定一個最簡單的block,并打上斷點角撞,運行到斷點后沛申,打開匯編(Debug--Debug Workflow -- Always Show Disassembly)。


3、在匯編中趁桃,查看block的類型贴捡,以及將要call的符號

4、點擊step into,進入到objc_retainBlock函數(shù)中,可看到將要訪問_Block_copy蝶念,此函數(shù)即可將 stack block 復(fù)制成 malloc block

5、繼續(xù)點擊step into會回到main的匯編廷蓉。因此_Block_copy是我們繼續(xù)探索的函數(shù)符號全封。打開1的Blocks工程。全局搜索_Block_copy桃犬,源碼如下(注意看注釋)

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    // block的真實結(jié)構(gòu)Block_layout
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    // 如果是釋放狀態(tài)刹悴,沒必要進行下一步處理,直接返回aBlock
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        // 如果是global類型的block攒暇,直接返回恒削。
        return aBlock;
    }
    else {
        // 如果不是global block,那么只有兩種情況:1肥橙、棧block(StackBlock)搏色,2、堆block(MallocBlock)
        // 在編譯階段当娱,暫時會將block標記成棧block散休,是因為在編譯階段胁勺,若是對block進行malloc開辟內(nèi)存的話络拌,會增加編譯器的壓力
        // 來到運行時(runtime)崭倘,block捕獲了外部變量坞淮,則需要將棧block 的大小付枫,malloc(size)開辟一個內(nèi)存空間
        // Its a stack block.  Make a copy.
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        // 開始copy
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        // invoke的copy
        result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);

            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#endif
        // reset refcount
        // 重置refcount和配置flags
        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.
        // 將block類型標記為堆block(MallocBlock)
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

6秤涩、通過源碼可知流程如下:
a)oc定義的block 不 捕獲外部變量 ---->編譯器--->global block---->運行時--->return global block
b)oc定義的block捕獲外部變量 ---->編譯器--->stack block ---->運行時_Block_copy--->malloc block
7后频、block的底層結(jié)構(gòu)為struct Block_layout結(jié)構(gòu)體臊泰,源碼如下(注意注釋):

struct Block_layout {
    // isa的指向:也可以說是block的類型:1、global block越走;2、stack block拧粪;3扁达、malloc block
    void * __ptrauth_objc_isa_pointer isa;
    // 標識
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // 執(zhí)行函數(shù)
    BlockInvokeFunction invoke;
    // block的信息描述
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • 7.1但绕、結(jié)構(gòu)體Block_layout的成員變量struct Block_description_1開始选脊,往下都是為可選參數(shù),也就是說脸甘,block的描述信息恳啥,可以在以下結(jié)構(gòu)體中選擇(但是具體如何設(shè)置,根據(jù)#define條件來配置丹诀,在哪兒配置钝的,我們不得而知翁垂,然我們可以通過反推法來了解,即不知道setter硝桩,通過getter來了解):
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    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
};
struct Block_descriptor_small {
    uint32_t size;
    int32_t signature;
    int32_t layout;
    /* copy & dispose are optional, only access them if
       Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
    int32_t copy;
    int32_t dispose;
};
  • 7.2 block desc的幾種情況
  • 7.2.1 Block_descriptor_1:搜索_Block_get_descriptor沿猜,源碼如下:
// getter 函數(shù)
static inline void *_Block_get_descriptor(struct Block_layout *aBlock)
{
    void *descriptor;
#if __has_feature(ptrauth_signed_block_descriptors)
    if (!(aBlock->flags & BLOCK_SMALL_DESCRIPTOR)) {
        descriptor =
                (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
    } else {
        uintptr_t disc = ptrauth_blend_discriminator(
                &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator);
        descriptor = (void *)ptrauth_auth_data(
                aBlock->descriptor, ptrauth_key_asda, disc);
    }
#elif __has_feature(ptrauth_calls)
    descriptor = (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
#else
    descriptor = (void *)aBlock->descriptor;
#endif
    return descriptor;
}
// setter函數(shù)
static inline void _Block_set_descriptor(struct Block_layout *aBlock, void *desc)
{
    aBlock->descriptor = (struct Block_descriptor_1 *)desc;
}
  • 7.2.2 Block_descriptor_2:全局搜索Block_descriptor_2,源碼如下(看下圖解釋):
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    desc += sizeof(struct Block_descriptor_1);// 看下圖解釋
    return (struct Block_descriptor_2 *)desc;
}
  • 7.2.3 Block_descriptor_3:全局搜索Block_descriptor_3碗脊,源碼如下(看上圖解釋):
    Block_descriptor_2存在啼肩,則指針偏移在desc的基礎(chǔ)上,加上Block_descriptor_1的大小望薄,再加上Block_descriptor_2的大小疟游,即可得到Block_descriptor_3。(desc3 = desc + sizeOf(desc1) + sizeOf(desc2))
    Block_descriptor_2不存在痕支,則指針偏移在desc的基礎(chǔ)上,加上Block_descriptor_1的大小蛮原,即可得到Block_descriptor_3卧须。(desc3 = desc + sizeOf(desc1) )
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    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;
}

block 調(diào)試

1、未捕獲外部變量:回到objc工程儒陨,在main.m添加如下代碼花嘶,并添加斷點:


2、捕獲外部變量:

3蹦漠、添加_Block_copy符號斷點椭员,運行時,先暫時關(guān)閉_Block_copy斷點笛园,因為在運行時會有一些系統(tǒng)的block會調(diào)用此函數(shù)進行復(fù)制隘击,我們只需要在自定義的block斷點停住時,把_Block_copy斷點激活即可研铆。打開Debug 匯編埋同。

3.1:我們通過lldb調(diào)試的指令register read讀取寄存器的情況(真機的寄存器名字為x0,x1等),最終看到在_Block_copy函數(shù)內(nèi)部的匯編block的類型的變化棵红,對應(yīng)上面的_Block_copy源碼即可凶赁。

3.2:繼續(xù)點擊step over,一直回到main.m時逆甜,在block調(diào)用處打上斷點虱肄。通過lldb調(diào)試的指令register read讀取寄存器的情況,如圖所示:

若外部變量為對象交煞,則會多一個copy和dispose豫喧。即7.1中的block信息描述選擇穷当。

<__NSStackBlock__: 0x7ffeefbff428>
 signature: "v8@?0"
 invoke   : 0x1000038e0 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__main_block_invoke)
 copy     : 0x100003910 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__copy_helper_block_e8_32s)
 dispose  : 0x100003950 (/Users/monan/Library/Developer/Xcode/DerivedData/LGProject-dyfiqisfsswhupfyzablyalixxxt/Build/Products/Debug/QLObjcTest`__destroy_helper_block_e8_32s)

3.3:signature的值可參考前面的文章類的方法底層,是一樣的。具體例子:lldb調(diào)試:[NSMethodSignature signatureWithObjCTypes:"v8@?0"];打印結(jié)果如下(注意注釋):

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"];
<NSMethodSignature: 0x7afa2cf5db0b894b>
    number of arguments = 1
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@?' // 證明了block的encoding類型是`@?`
        flags {isObject, isBlock}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}

block 捕獲

【1】無__block修飾

  • 1.1 在main.m中設(shè)計代碼如下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [NSObject alloc];
        void (^ qlBlock)(void) = ^{
            NSLog(@"---%@---",obj);
        };
        qlBlock();
    }
    return 0;
}
  • 1.2 通過xcrun將源碼編譯成c/c++。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

將代碼進行排版岛抄,清除一些類型強轉(zhuǎn),簡化后的代碼如下:

  • 1.3 從入口函數(shù)main()中可以看到,^{};最終翻譯成函數(shù)__main_block_impl_0(),該函數(shù)的入?yún)⒎治觯?/li>
  • a)__main_block_func_0是一個函數(shù)指針平斩,是block回調(diào)執(zhí)行的代碼塊
  • b)(重點)&__main_block_desc_0_DATA是取結(jié)構(gòu)體__main_block_desc_0的地址,該結(jié)構(gòu)體賦值情況咽块,即:
 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

具體對應(yīng)的變量為:

size_t reserved = 0;
size_t Block_size = sizeof(struct __main_block_impl_0);
void (copy)(struct __main_block_impl_0, struct __main_block_impl_0) = __main_block_copy_0;
void (
dispose)(struct __main_block_impl_0*) = __main_block_dispose_0;

  • c)obj即我們定義的obj變量绘面,obj作為參數(shù)傳入block內(nèi)部生成的obj變量,并將外部obj賦值給內(nèi)部obj侈沪。由于obj是一個NSObject *的指針揭璃,此處obj傳入相當于block生成的obj與外部的obj指向同一片堆空間。(block捕獲外部變量的小結(jié)在上面的 block 底層結(jié)構(gòu)小結(jié)
  • d)570425344數(shù)字參數(shù)亭罪,是一個暫時用不到的參數(shù)瘦馍。暫不考慮。

【2】有__block修飾

我們在NSObject *obj前加入__block应役,然后 按照第【1】步的步驟情组,將main.m編譯成c/c++。
將代碼進行排版箩祥,清除一些類型強轉(zhuǎn)院崇,簡化后的代碼如下:

  • 2.1 與前面的無 __block 修飾的外部變量捕獲相比,加了__block后袍祖,外部變量NSObject *obj邏輯編譯成了一個結(jié)構(gòu)體__Block_byref_obj_0 obj底瓣,該結(jié)構(gòu)體源碼如下:
  • 2.2 用__block修飾的外部遍歷,block構(gòu)造函數(shù)__main_block_impl_0傳入的是&obj蕉陋,也就是obj本身的地址捐凭,也就是說,外部的obj隨著block內(nèi)部的obj的變化而變化寺滚。
  • 2.3 無__block修飾的外部變量柑营,block構(gòu)造函數(shù)__main_block_impl_0傳入的是obj,也就是obj指針指向的堆地址村视。

【3】_Block_object_assign

  • 【3.1】 通過上面將block編譯成c/c++文件的分析官套。我們可以看到在封裝block信息描述的結(jié)構(gòu)體desc中,傳入了一__main_block_copy_0函數(shù)和一個__main_block_dispose_0函數(shù)蚁孔。兩個函數(shù)的實現(xiàn)如下:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign(&dst->obj, src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}

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

_Block_object_assign()傳入了三個參數(shù):1奶赔、&dst->obj:目標block的obj地址;2杠氢、src->obj:原block的obj變量站刑;3、8:BLOCK_FIELD_IS_BYREF鼻百,表示該對象用__block 修飾了绞旅。

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.

即:

  • 捕獲的外部變量為普通oc對象摆尝,則傳入3;
  • 捕獲的外部變量為其他block因悲,則傳入7堕汞;
  • 捕獲的外部變量為用__block修飾的變量,則傳入8晃琳;
  • 【3.2】 打開Blocks.xcodeproj工程讯检,全局搜索_Block_object_assign,找到源碼如下:
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;
    }
}

分析:
通過flags & BLOCK_ALL_COPY_DISPOSE_FLAGS得到捕獲的變量是什么類型卫旱,通過switch分支進行分類處理人灼。

  • 3.2.1 case BLOCK_FIELD_IS_OBJECT: 如果捕獲的變量是oc object ,則將捕獲的object進行copy顾翼。(通過_Block_retain_object()回調(diào)給_Block_retain_object_default()投放,并將原block賦值給目標block*dest = object;
  • 3.2.2 case BLOCK_FIELD_IS_BLOCK: 如果捕獲的對象是一個block,則走_Block_copy()函數(shù)暴构,并將結(jié)果賦值給目標block跪呈。
  • 3.2.3(后面重點分析case BLOCK_FIELD_IS_BYREF : 如果捕獲的變量是用__block修飾的或者__weak __block修飾的,將原block通過_Block_byref_copy()賦值給目標block取逾。
  • 3.2.4 其余情況均是將原block直接賦值給目標block*dest = object;

【4】_Block_byref_copy

全局搜索_Block_byref_copy,得到源碼如下:

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;
}
  • 【4.1】判斷語句if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0)苹支,其中BLOCK_REFCOUNT_MASK引用計數(shù)相關(guān)的掩碼砾隅。如果此處判斷 == 0,說明引用計數(shù)為0债蜜,并沒有將block copy到堆上晴埂。反之(src->forwarding->flags & BLOCK_REFCOUNT_MASK) != 0,則說明已經(jīng)將原block copy到堆上寻定。接著判斷flags是否需要釋放儒洛,需要釋放則進行flags的重新賦值
    -【4.2】未copy到堆上狼速,流程如圖所示:

    -【4.3】判斷語句if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE)如果成立琅锻,說明已經(jīng)捕獲到了外部變量,具體如圖所示:

【5】byref_keep

由上圖我們可知byref_keep()是為了保存(毕蚝活)外部捕獲變量的生命周期恼蓬,如果不進行keep,則有可能會出現(xiàn)src為空了之后僵芹,開辟的空間的內(nèi)容也被清空了处硬。
byref_keep的源碼為:

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

返回到main_byref.cpp文件中,找到struct __Block_byref_obj_0{}結(jié)構(gòu)體拇派,內(nèi)部的copydispose函數(shù)分別賦值給了byref_keepbyref_destroy荷辕。也就是說在編譯后的cpp文件中凿跳,在main函數(shù)里,__block修飾的NSObject *obj疮方,編譯所生成的結(jié)構(gòu)體的賦值控嗜,__Block_byref_id_object_copy_131賦值給了結(jié)構(gòu)體__Block_byref_obj_0copy函數(shù);__Block_byref_id_object_dispose_131賦值給了__Block_byref_obj_0dispose函數(shù)案站。
也就是說:

byref_keep = __Block_byref_id_object_copy_131函數(shù)
byref_destroy = __Block_byref_id_object_dispose_131函數(shù)

由此可知躬审,byref_keep函數(shù)的調(diào)用,是調(diào)用__Block_byref_obj_0copy函數(shù)蟆盐。也就是把_Block_object_assign調(diào)用一遍承边,即

// (*src2->byref_keep)(copy, src);
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign(dst + 40, * (src + 40), 131);
}
  • a)dst:即byref_keep函數(shù)的第1個參數(shù)copy:新開辟的block(目標block)。
  • b)src:即byref_keep函數(shù)的第2個參數(shù)src:原來的block石挂。
  • c)dst+40博助、src + 40,是在目標block的首地址平移40字節(jié)痹愚,對應(yīng)結(jié)構(gòu)體__Block_byref_obj_0內(nèi)存結(jié)構(gòu)富岳,平移40字節(jié),得到的是NSObject *obj拯腮,計算方式如下:
struct __Block_byref_obj_0 {
    void *__isa; // 8
    __Block_byref_obj_0 *__forwarding;// 8
    int __flags;        // 4
    int __size;         // 4
    void (*__Block_byref_id_object_copy)(void*, void*);// 8
    void (*__Block_byref_id_object_dispose)(void*);// 8
    NSObject *obj;
};

_Block_object_assign(dst + 40, * (src + 40), 131);變成為_Block_object_assign(obj, * obj, 131);窖式。由上面_Block_object_assign函數(shù)的源碼可知,此刻的switch分支动壤,走的就是BLOCK_FIELD_IS_OBJECT分支萝喘。
因此,block捕獲__block修飾的外部變量琼懊,

總結(jié)

  • block的本質(zhì)是一個結(jié)構(gòu)體阁簸,并由結(jié)構(gòu)體的構(gòu)造函數(shù)初始化和賦值
  • block的類型有3種哼丈,global block启妹,stack blockmalloc block醉旦。
  • block 不捕獲外部變量則為 global block饶米。
  • block捕獲外部變量編譯時為stack block運行時_Block_copy后變成malloc block髓抑。
  • 結(jié)構(gòu)體impl內(nèi)部有4個成員變量:isa咙崎、Flags、Reserved吨拍、FuncPtr褪猛,在block構(gòu)造函數(shù)賦值時,將isa賦值為默認的NSConcreteStackBlock地址(此為編譯階段羹饰,運行時這個isa會賦值成真實類型)伊滋,F(xiàn)uncPtr 的賦值是定義的block執(zhí)行代碼塊碳却。
  • block結(jié)構(gòu)體中的Block_descriptor_1Block_byref一樣內(nèi)存是連續(xù)的可通過指針平移來獲取對應(yīng)位置的值。
    Block_descriptor_1---Block_descriptor_2---Block_descriptor_3內(nèi)存是連續(xù)的笑旺,
    Block_byref---Block_byref_2---Block_byref_3內(nèi)存是連續(xù)的
  • block捕獲外部變量昼浦,是在block內(nèi)部生成對應(yīng)的成員變量,并通過構(gòu)造函數(shù)將捕獲的變量賦值給內(nèi)部生成的成員變量 --(值拷貝)筒主。
  • 捕獲用__block修飾的外部變量(指針拷貝)3層copy
    (a)將block從copy到
    (b)接著block捕獲Block_byref結(jié)構(gòu)體关噪,并對其進行copy
    (c)Block_byref對內(nèi)部的變量(本文為NSObject *obj)進行copy。
最后編輯于
?著作權(quán)歸作者所有,轉(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
  • 正文 為了忘掉前任崇堰,我火速辦了婚禮沃于,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘海诲。我一直安慰自己繁莹,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布特幔。 她就那樣靜靜地躺著咨演,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚯斯。 梳的紋絲不亂的頭發(fā)上薄风,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天饵较,我揣著相機與錄音,去河邊找鬼遭赂。 笑死循诉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的撇他。 我是一名探鬼主播茄猫,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼困肩!你這毒婦竟也來了划纽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 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)自己被綠了卸耘。 大學(xué)時的朋友給我發(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)容