OC底層探索25-深入淺出Block

block幾乎天天都在使用,也是面試題高發(fā)區(qū)。可是原理還是有點(diǎn)晦澀的,現(xiàn)在就靜下心來聽我慢慢道來!

1、循環(huán)引用的解決

1.1 循環(huán)引用的造成

  1. 正常情況:


  2. 循環(huán)引用:


  • 兩個(gè)對象相互引用,導(dǎo)致兩個(gè)對象即使使用完成后也不能正常進(jìn)行dealloc,這就是循環(huán)引用
  1. Block的循環(huán)引用:
self.block = ^{
    [self sayHello];
};
  • Block是極其容易造成循環(huán)引用的,例如下方這段代碼。self和block相關(guān)引用

1.2 循環(huán)引用的解決

解決循環(huán)引用常見的方式有以下幾種:

  1. __weak + __strong + dance,利用中介者模式;
  2. __block修飾引用對象,同樣是利用中介者模式但是需要手動釋放引用對象;
  3. self作為參數(shù)傳入
  4. 使用NSProxy;
1.2.1 __weak + __strong + dance
@property(nonatomic, copy) void(^block)(void);

__weak typeof(self) weakSelf = self;
self.block = ^(void){
    __strong  typeof(weakSelf) strongSelf = weakSelf;
     NSLog(@"%@",strongSelf.name);
};
1.2.2 __block修飾引用對象
@property(nonatomic, copy) void(^block)(void);

__block UIViewController *vc = self;
self.block = ^(void){
    NSLog(@"%@",vc.name);
    vc = nil;   //手動釋放
};
  • 在Block中對引用參數(shù)進(jìn)行修改必須使用__block修飾,第四部分會分析;
  • 需要注意的是:block必須調(diào)用,不調(diào)用依舊會造成循環(huán)引用;
1.2.3 self作為參數(shù)傳入
@property(nonatomic, copy) void(^block)(UIViewController *);

self.block = ^(UIViewController *vc){
    NSLog(@"%@",vc.name);
};

//調(diào)用
self.block(self);
  • 這種寫法在ReactCocoa中還挺常見的;
1.2.4 NSProxy 模板類

NSProxy 和 NSObject是同級的一個(gè)類,也可以說是一個(gè)虛擬類,只是實(shí)現(xiàn)了NSObject的協(xié)議;

@interface HRTestProxy : NSProxy
@property (nonatomic, strong, nullable) NSObject *target;
@end

@implementation HRTestProxy
// NSProxy實(shí)現(xiàn)邏輯和方法慢速轉(zhuǎn)發(fā)一致,通過模板類對詳細(xì)進(jìn)行轉(zhuǎn)發(fā)
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end
-(void)demo{
    HRTestProxy *myProxy = [HRTestProxy alloc];
    myProxy.target = self;
    self.block = ^(void){
        [myProxy performSelector:@selector(say)];
    };
    //調(diào)用
    self.block();
}

-(void)say{
    NSLog(@"%@",self.name);
}
  • 通過利用虛基類NSProxy的特性將self -> block -> self,改為self -> block -> NSProxy的結(jié)構(gòu),從而完成循環(huán)引用的打破;

2、Block結(jié)構(gòu)分析

2.1 block最基本形態(tài)

分析結(jié)構(gòu)一定是離不開clang,

clang命令:
clang -rewrite-objc main.m -o main.cpp

void (^mallocBlock)(void) = ^void {
    NSLog(@"HR_Block");
};

將上方代碼進(jìn)行編譯;


  • block在編譯之后是一個(gè)結(jié)構(gòu)體/對象isa:_NSConcreteStackBlockobjc_object是一致的;
  • 初始化:block結(jié)構(gòu)體的構(gòu)造函數(shù):&__main_block_impl_0(方法, &參數(shù)),將isa、大小參數(shù)、執(zhí)行函數(shù)等信息賦值;
  • 調(diào)用:從結(jié)構(gòu)體__main_block_impl_0里獲取FuncPtr函數(shù)指針,也就是__main_block_func_0(block自己),并將block自己傳入;這個(gè)位置可以理解為OC方法中的隱藏參數(shù): (id self,Sel cmd)
2.1 補(bǔ)充 block三種類型

__NSGlobalBlock__全局block,存儲在全局區(qū)

void(^block)(void) = ^{
    NSLog(@"HR");
};
  • 此時(shí)的block無參也無返回值,屬于全局block

__NSMallocBlock__堆區(qū)block

int a = 10;
void(^block)(void) = ^{
    NSLog(@"HR - %d", a);
};
  • 此時(shí)的block會訪問外界變量,即底層拷貝a,所以是堆區(qū)block

__NSStackBlock__棧區(qū)block

int a = 10;
void(^__weak block)(void) = ^{
    NSLog(@"HR - %d", a);
};
  • 通過__weak不進(jìn)行強(qiáng)持有,block就還是棧區(qū)block;

2.2 捕獲外界變量(值拷貝)

int a = 10;
void (^mallocBlock)(void) = ^void {
    NSLog(@"HR_Block - %d",a);
};

mallocBlock();
  • block內(nèi)部使用到了外部變量之后,會導(dǎo)致block結(jié)構(gòu)體有什么變化呢?;


  • 初始化:發(fā)現(xiàn)__main_block_impl_0結(jié)構(gòu)體內(nèi)多了一個(gè)int a,也就是說block會把捕獲的外界變量進(jìn)行copy到自身結(jié)構(gòu)里,當(dāng)然這些操作都是編譯器幫我們完成了;

  • 調(diào)用沒有變化,只是在體內(nèi)多了一個(gè)a的取值.通過觀察在__main_block_func_0里參數(shù)int a = __cself->a;進(jìn)行了值拷貝

2.1.1 值拷貝驗(yàn)證
  • block外部a的地址;
  • block內(nèi)部的a的地址,兩個(gè)地址完全是不同.驗(yàn)證了之前值拷貝的說法;

2.3 __block聲明變量(指針拷貝

在2.2中出現(xiàn)了值拷貝,而且在block中也不允許使用修改捕獲參數(shù),想過要block內(nèi)部修改就需要__block聲明變量。

__block int a = 10;
void (^mallocBlock)(void) = ^void {
    NSLog(@"HR_Block - %d",a++);
};

編譯之后block結(jié)構(gòu)體的變化很大;


  • __block修改的被捕獲變量被聲明成__Block_byref_a_0這樣的一個(gè)結(jié)構(gòu)。把指針的地址和值進(jìn)行保存,所以才可以在block塊內(nèi)完成值的修改;
  • 調(diào)用:__Block_byref_a_0 *a = __cself->a;中的賦值和直接捕獲是不一樣的,這里是引用了__Block_byref_a_0的地址。在a++操作的時(shí)候(a->__forwarding->a)++);因?yàn)樵诔跏蓟臅r(shí)候保存的就是_a->__forwarding;
  • _Block_object_assign,_Block_object_dispose就是對外界捕獲參數(shù)的操作,在block的三層拷貝的時(shí)候會著重分析;這里就需要簡單知道:block需要對外界變量進(jìn)行持有;因?yàn)檫@里都是c++的操作,無法操作ARC完成引用計(jì)數(shù)的操作;
  • 如果__block 修飾的是一個(gè)對象的話:__Block_byref_id_object_copy_xx會出現(xiàn)這樣一個(gè)方法.

3、block模板類型

源碼位置:
在工程中增加符號斷點(diǎn)_Block_copy

libclosure源碼下載想要繼續(xù)深入就需要分析源碼;

3.1 Block_layout

Block真正的結(jié)構(gòu)類型,之前看到__main_block_impl_0都是以它為模板生成的,所以Block_layout是一個(gè)模板類型;

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
    // 所有捕捉的外部函數(shù)聲明
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};
// 涉及到__block修飾的外部變量時(shí)會出現(xiàn)
#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;  // 保存block的方法簽名
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
3.1 Block_layout結(jié)構(gòu)圖:
Block_layout

3.2 __block修飾后Block_byref模板

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;
};
  • 在前面出現(xiàn)的__Block_byref_a_0就是以Block_byref為模板進(jìn)行創(chuàng)建的。
  • 如果__block引用的是一個(gè)對象則會出現(xiàn)Block_byref_2中的這部分結(jié)構(gòu)。

3.3 Block的方法簽名

利用Block_layout結(jié)構(gòu)獲取Block方法簽名篇幅有點(diǎn)大,就新起了一篇;代碼很有意思?xì)g迎閱讀!!

4、Block的多次拷貝

編譯器是不可以直接在堆區(qū)進(jìn)行創(chuàng)建的需要進(jìn)行malloc的內(nèi)存申請,可是__NSMallocBlock__這個(gè)類型的Block就是存儲在堆區(qū)的,那么就一定進(jìn)行過malloc操作;

4.1 _Block_copy-lldb調(diào)試

還記得之前提到過的_Block_copy這個(gè)符號斷點(diǎn)嗎?

  • 通過lldb獲取寄存器rax(模擬器)的值,可以看到當(dāng)前是一個(gè)__NSStackBlock__

等到方法執(zhí)行完之后,在查看寄存器rax(模擬器)的值

  • 進(jìn)過_Block_copy函數(shù)之后,Block從__NSStackBlock__變成了一個(gè)__NSMallocBlock__,這個(gè)函數(shù)就很關(guān)鍵了。

4.2 _Block_copy-源碼 第一次拷貝

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
    if (!arg) return NULL;
    // 這就是block第一次copy    
    aBlock = (struct Block_layout *)arg;
    // 如果Block需要釋放則直接釋放
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 如果Block是一個(gè)全局Block,不做任何操作
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // 從堆區(qū)申請內(nèi)存空間,并將棧區(qū)內(nèi)容移動到堆區(qū)
        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)
        // 完成賦值、標(biāo)示符的修改
        result->invoke = aBlock->invoke;
#endif
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // 將Block的類型轉(zhuǎn)換為_NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
  • 代碼很清晰,在堆區(qū)申請空間,并將棧區(qū)所有數(shù)據(jù)移動到堆區(qū);
  • 最后將isa指向_NSConcreteMallocBlock,完成類型的修改;

4.3 _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)) {
      case BLOCK_FIELD_IS_OBJECT:
        // _Block_retain_object沒有做任何處理
        _Block_retain_object(object);
        // Block對捕獲的外界參數(shù)進(jìn)行了一次強(qiáng)引用,這就是導(dǎo)致循環(huán)引用的本質(zhì)
        *dest = object;
        break;
      case BLOCK_FIELD_IS_BLOCK:
        // 如果外界參數(shù)是一個(gè)Block類型,那么再進(jìn)行一次_Block_copy
        *dest = _Block_copy(object);
        break;
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // 外界參數(shù)被__block修飾
        *dest = _Block_byref_copy(object);
        break;
    //還有些其他類型,這里就省略了
    }
}

4.4 _Block_byref_copy-源碼 第三次拷貝

__block 捕獲外界變量的操作 內(nèi)存拷貝 以及常規(guī)處理

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) {
        // 申請了一片堆區(qū)控件
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        //block內(nèi)部持有的Block_byref 和 外界的Block_byref 所持有的對象是同一個(gè),這也是為什么__block修飾的變量具有修改能力
        //copy 和 scr 的地址指針達(dá)到了完美的同一份拷貝,目前只有持有能力
        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) {
            //Block_byref_2是結(jié)構(gòu)體,__block修飾的可能是對象,對象通過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_131
            (*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;
}
  • 如果被__block修飾之后會進(jìn)入進(jìn)行第三次拷貝,如果本次拷貝的變量又支持copy那么將進(jìn)行更深層次的_Block_object_assign;

4.5 多層拷貝流程圖

如果需要可編譯的libclosure源碼可以私信我。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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