iOS Block底層探索

蘋(píng)果官方資源opensource
objc4-838可編譯聯(lián)調(diào)源碼
libclosure源碼

本章節(jié)探究:
1.Block的類(lèi)型
2.Block的實(shí)質(zhì)
3.內(nèi)容捕獲
4.__block的底層原理
5.Block的循環(huán)引用
6.面試題

一、Block的類(lèi)型

1.Global Block - 全局
__NSGlobalBlock__
2.Malloc Block - 堆
__NSMallocBlock__
3.Stack Block - 棧
__NSStackBlock__

__weak修飾block會(huì)在出方法棧的時(shí)候立馬被銷(xiāo)毀。

Block的類(lèi)型總結(jié):
全局Block:沒(méi)有捕獲外部的局部變量;只使用了全局變量/靜態(tài)變量。
堆Block:捕獲了局部變量;賦值給了強(qiáng)引用。
棧Block:捕獲了局部變量;賦值給了弱引用。

為什么Block要?copy關(guān)鍵字修飾?
Block在創(chuàng)建的時(shí)候,它的內(nèi)存是分配在方法棧上的,?不是在堆上。
棧區(qū)的特點(diǎn)是:對(duì)象隨時(shí)有可能被銷(xiāo)毀,?旦被銷(xiāo)毀,在調(diào)?的時(shí)候,就會(huì)造成系統(tǒng)的崩潰。所以我們要使?copy把它拷?到堆上。
ARC下,對(duì)于Block使?copystrong其實(shí)都?樣,因?yàn)?code>block的retain就是?copy來(lái)實(shí)現(xiàn)的。所以ARCblock使? copystrong 都可以。

二、Block的本質(zhì)

把上面代碼編譯成.cpp文件

$ clang -rewrite-objc main.m

打開(kāi)main.cpp,找到main函數(shù)

main.cpp

被編譯之后Block就變成了__main_block_impl_0的數(shù)據(jù)結(jié)構(gòu)了

__main_block_impl_0

1.NSObject *objc就是捕獲的外部局部變量;
2.impl.isa = &_NSConcreteStackBlock;這行代碼可以看出Block是一個(gè)對(duì)象;
3.Block在創(chuàng)建的時(shí)候它是棧Block

棧Block具備捕獲外部局部變量的能力。
Block不是在調(diào)用的時(shí)候才捕獲外部局部變量,而是在聲明的時(shí)候就已經(jīng)捕獲了。

1.匯編分析Block的底層調(diào)用邏輯
  • 打開(kāi)匯編模式Always Show Disassembly

objc_retainBlock打上符號(hào)斷點(diǎn),看看它底層怎么調(diào)用的

_Block_copy的實(shí)現(xiàn)是放在libclosure源碼里的,參數(shù)是block

打開(kāi)objc4源碼找到objc_retainBlock

objc_retainBlock源碼聲明
2.源碼分析Block的底層調(diào)用邏輯

打開(kāi)libclosure源碼找到_Block_copy

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 拷貝 block,
// 如果原來(lái)就在堆上,就將引用計(jì)數(shù)加 1;
// 如果原來(lái)在棧上,會(huì)拷貝到堆上,引用計(jì)數(shù)初始化為 1,并且會(huì)調(diào)用 copy helper 方法(如果存在的話);
// 如果 block 在全局區(qū),不用加引用計(jì)數(shù),也不用拷貝,直接返回 block 本身
// 參數(shù) arg 就是 Block_layout 對(duì)象,
// 返回值是拷貝后的 block 的地址
// 運(yùn)行?stack -》malloc
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    // 如果 arg 為 NULL,直接返回 NULL

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    // 強(qiáng)轉(zhuǎn)為 Block_layout 類(lèi)型
    aBlock = (struct Block_layout *)arg;
    const char *signature = _Block_descriptor_3(aBlock)->signature;
    
    // 1.如果現(xiàn)在已經(jīng)在堆上
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        // 就只將引用計(jì)數(shù)加 1
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 2.如果 block 在全局區(qū),不用加引用計(jì)數(shù),也不用拷貝,直接返回 block 本身
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        // 3.block 現(xiàn)在在棧上,現(xiàn)在需要將其拷貝到堆上
        // 在堆上重新開(kāi)辟一塊和 aBlock 相同大小的內(nèi)存
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        // 開(kāi)辟失敗,返回 NULL
        if (!result) return NULL;
        // 將 aBlock 內(nèi)存上的數(shù)據(jù)全部復(fù)制新開(kāi)辟的 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;
#endif
        // reset refcount
        // 將 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清為 0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 將 result 標(biāo)記位在堆上,需要手動(dòng)釋放;并且引用計(jì)數(shù)初始化為 1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // copy 方法中會(huì)調(diào)用做拷貝成員變量的工作
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // isa 指向 _NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

參數(shù)argblock,它會(huì)先把block強(qiáng)轉(zhuǎn)成Block_layout這個(gè)結(jié)構(gòu)體類(lèi)型。
Block的本質(zhì)就是Block_layout結(jié)構(gòu)體。(后面會(huì)分析)

  • 1.aBlock已經(jīng)在堆上,只將引用計(jì)數(shù)+1;
  • 2.aBlock在全局區(qū),不做操作返回出去;
  • 3.aBlock在棧上,現(xiàn)在需要將其拷貝到堆上,在堆上重新開(kāi)辟一塊和aBlock相同大小的內(nèi)存。(原本的棧上的在出方法棧的時(shí)候就釋放掉了)
  • Block_layout結(jié)構(gòu)體

(注意:Block_layout擁有Block_descriptor_2里的copydispose函數(shù)是在捕獲了堆區(qū)外部局部變量的時(shí)候才會(huì)有。用于這個(gè)變量的引用計(jì)數(shù)管理)

  • _Block_call_copy_helper函數(shù)的作用是拷貝aBlock的成員變量的工作。
_Block_call_copy_helper
3.為什么Block需要調(diào)用的時(shí)候才會(huì)執(zhí)行
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    int a = 10;
    void (^block)(void) = ^ {
        NSLog(@"%d",a);
    };
    block();
    return NSApplicationMain(argc, argv);
}

main.m編譯成main.cpp

$ $ clang -rewrite-objc main.m

打開(kāi)main.cpp找到mian函數(shù)

來(lái)看看__main_block_impl_0結(jié)構(gòu)體構(gòu)造方法和__main_block_func_0函數(shù)實(shí)現(xiàn)部分:

這里傳遞的a是指針(不是地址)。

struct __block_impl impl;指定了isaflags、FuncPtr。

__block_impl
__main_block_desc_0

__main_block_impl_0結(jié)構(gòu)體與源碼里的Block_layout是一樣的。這就證實(shí)了源碼分析部分沒(méi)有錯(cuò)。

block聲明就是把方法實(shí)現(xiàn)保存到了__block_impl這個(gè)結(jié)構(gòu)體中去;等到調(diào)用的時(shí)候從這里取出函數(shù)實(shí)現(xiàn)地址直接調(diào)用。

三、內(nèi)容捕獲

  • 捕獲棧區(qū)的外部局部變量
-(void)test1 {
    int a = 10;
    NSLog(@"a--%p",&a);
    
    void (^block)(void) = ^ {
        NSLog(@"a--%p",&a);
    };
    block();
}

/**
打印結(jié)果:
a--0x7ff7bf79c29c
a--0x60000137c2f0
*/

很明顯兩個(gè)變量a同一個(gè)東西,block外部的a是棧區(qū)地址,block外部的a是堆區(qū)地址。
因?yàn)樵?code>block在棧區(qū)的時(shí)候就已經(jīng)捕獲了a,當(dāng)blockcopy到堆區(qū)時(shí),原本捕獲的成員變量也需要在堆區(qū)開(kāi)辟內(nèi)存。

如果需要改變?cè)?code>a的值需要使用__block來(lái)修飾a

-(void)test1 {
    __block int a = 10;
    NSLog(@"a--%p",&a);
    
    void (^block)(void) = ^ {
        NSLog(@"a--%p",&a);
        a++;
    };
    block();
    NSLog(@"a: %d", a);
}
/**
打印結(jié)果:
a--0x7ff7bf79c29c
a--0x60000137c2f0
a: 11
*/
  • 捕獲堆區(qū)的外部局部變量
-(void)test2 {
    NSObject *objc = [NSObject new];
    NSLog(@"%@",objc);
    NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)));
    
    NSLog(@"-------------------");
    
    void (^block)(void) = ^ {
        NSLog(@"%@",objc);
        NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    
    block();
}

/**
打印結(jié)果:
<NSObject: 0x600003a00310>
1
-------------------
<NSObject: 0x600003a00310>
3
*/

可以看到objc的打印是同一個(gè)東西。這是因?yàn)?code>objc所指向的內(nèi)存分配本身就在堆區(qū),block在捕獲到堆區(qū)的外部局部變量時(shí),不需要另外開(kāi)辟內(nèi)存空間,只需要堆區(qū)block的成員變量指針指向同一塊內(nèi)存空間即可。
因?yàn)闂^(qū)block和堆區(qū)block的成員變量都指向objc實(shí)際的堆區(qū)內(nèi)存區(qū)域,所以objc的引用計(jì)數(shù)+2。

  • 是否能捕獲 全局變量、靜態(tài)變量
int b = 20; // 全局變量
static int c = 30; // 靜態(tài)變量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    __block int a = 10; // 局部變量
    NSLog(@"a--%p",&a);
    NSLog(@"b--%p",&b);
    NSLog(@"c--%p",&c);
    void (^block)(void) = ^ {
        NSLog(@"捕獲a--%p",&a);
        NSLog(@"捕獲b--%p",&b);
        NSLog(@"捕獲c--%p",&c);
        a++; // 必須使用__block修飾才能修改局部變量a的值
    };
    block();
    NSLog(@"a: %d", a);
    return NSApplicationMain(argc, argv);
}
2022-06-13 20:33:01.485055+0800 Block的本質(zhì)[44760:11096843] a--0x7ff7b3368248
2022-06-13 20:33:01.485157+0800 Block的本質(zhì)[44760:11096843] b--0x10cb9fd58
2022-06-13 20:33:01.485189+0800 Block的本質(zhì)[44760:11096843] c--0x10cb9fd5c
2022-06-13 20:33:01.485265+0800 Block的本質(zhì)[44760:11096843] 捕獲a--0x6000004ab938
2022-06-13 20:33:01.485300+0800 Block的本質(zhì)[44760:11096843] 捕獲b--0x10cb9fd58
2022-06-13 20:33:01.485327+0800 Block的本質(zhì)[44760:11096843] 捕獲c--0x10cb9fd5c
2022-06-13 20:33:01.485347+0800 Block的本質(zhì)[44760:11096843] a: 11

可以看到捕獲前后變量a的地址明顯不一樣,那說(shuō)明他倆不是一個(gè)東西。

再把mian.m編譯成main.cpp

(請(qǐng)注意這里的a.__forwarding->a,與__block有關(guān)后面會(huì)講)

找到__main_block_impl_0結(jié)構(gòu)體聲明的地方

__main_block_impl_0

Block只捕獲了外部局部變量a;沒(méi)有捕獲全局b和靜態(tài)c。

Block不會(huì)捕獲全局變量和靜態(tài)變量。
Block捕獲的變量是在Block內(nèi)部生成新的指針指向捕獲變量的內(nèi)存(淺拷貝)。

四、__block修飾局部變量的底層原理

block內(nèi)部可以修改全局變量和靜態(tài)變量的值,但是不允許修改局部變量的值。要箱子block內(nèi)部修改局部變量的值需要用__block修飾。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    __block NSObject *obc = [NSObject new];
    void (^block)(void) = ^ {
        NSLog(@"%@",obc);
    };
    block();
    return NSApplicationMain(argc, argv);
}

mian.m編譯成main.cpp,找到main函數(shù)

__main_block_impl_0是構(gòu)造函數(shù),它傳遞一個(gè)捕獲外部局部變量(__Block_byref_obc_0 *)&obc。

__main_block_impl_0

同時(shí)在構(gòu)造__Block_byref_obc_0的時(shí)候傳遞了__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131這兩個(gè)函數(shù)地址

__Block_byref_obc_0

這里的__forwarding是指向__Block_byref_obc_0本身。

  • _Block_object_assign的分析

__Block_byref_id_object_copy_131的調(diào)用時(shí)機(jī)是在Block從??截惖蕉训臅r(shí)候,也就是Block源碼分析環(huán)節(jié)里的_Block_call_copy_helper函數(shù)

_Block_call_copy_helper

__Block_byref_id_object_copy_131的底層其實(shí)就是調(diào)用了_Block_object_assign函數(shù)
在源碼中找到_Block_object_assign的聲明

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// 當(dāng) block 和 byref 要持有對(duì)象時(shí),它們的 copy helper 函數(shù)會(huì)調(diào)用這個(gè)函數(shù)來(lái)完成 assignment,
// 參數(shù) destAddr 其實(shí)是一個(gè)二級(jí)指針,指向真正的目標(biāo)指針
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];
        ********/
        // 默認(rèn)什么都不干,但在 _Block_use_RR() 中會(huì)被 Objc runtime 或者 CoreFoundation 設(shè)置 retain 函數(shù),
        // 其中,可能會(huì)與 runtime 建立聯(lián)系,操作對(duì)象的引用計(jì)數(shù)什么的
        _Block_retain_object(object);
        // 使 dest 指向的目標(biāo)指針指向 object
        *dest = object;
        break;

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

       // 使 dest 指向的拷貝到堆上object
        *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 指向的拷貝到堆上的byref
        *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 指向的目標(biāo)指針指向 object
        *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 指向的目標(biāo)指針指向 object
        *dest = object;
        break;

      default:
        break;
    }
}

如果是__block修飾的局部變量,它就會(huì)走case BLOCK_FIELD_IS_BYREF代碼段,去調(diào)用_Block_byref_copy(object);

_Block_byref_copy的源碼聲明:

// 1. 如果 byref 原來(lái)在堆上,就將其拷貝到堆上,拷貝的包括 Block_byref、Block_byref_2、Block_byref_3,
//    被 __weak 修飾的 byref 會(huì)被修改 isa 為 _NSConcreteWeakBlockVariable,
//    原來(lái) byref 的 forwarding 也會(huì)指向堆上的 byref;
// 2. 如果 byref 已經(jīng)在堆上,就只增加一個(gè)引用計(jì)數(shù)。
// 參數(shù) dest是一個(gè)二級(jí)指針,指向了目標(biāo)指針,最終,目標(biāo)指針會(huì)指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // arg 強(qiáng)轉(zhuǎn)為 Block_byref * 類(lèi)型
    struct Block_byref *src = (struct Block_byref *)arg;

    // 引用計(jì)數(shù)等于 0
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        // 為新的 byref 在堆中分配內(nèi)存
        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
        // 新 byref 的 flags 中標(biāo)記了它是在堆上,且引用計(jì)數(shù)為 2。
        // 為什么是 2 呢?注釋說(shuō)的是 non-GC one for caller, one for stack
        // one for caller 很好理解,那 one for stack 是為什么呢?
        // 看下面的代碼中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相當(dāng)于引用了 copy
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        // 堆上 byref 的 forwarding 指向自己
        copy->forwarding = copy; // patch heap copy to point to itself
        // 原來(lái)?xiàng)I系?byref 的 forwarding 現(xiàn)在也指向堆上的 byref
        src->forwarding = copy;  // patch stack to point to heap copy
        // 拷貝 size
        copy->size = src->size;

        // 如果 src 有 copy/dispose helper
        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
            // 取得 src 和 copy 的 Block_byref_2
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            // copy 的 copy/dispose helper 也與 src 保持一致
            // 因?yàn)槭呛瘮?shù)指針,估計(jì)也不是在棧上,所以不用擔(dān)心被銷(xiāo)毀
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            // 如果 src 有擴(kuò)展布局,也拷貝擴(kuò)展布局
            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);
                // 沒(méi)有將 layout 字符串拷貝到堆上,是因?yàn)樗?const 常量,不在棧上
                copy3->layout = src3->layout;
            }
            // 調(diào)用 copy helper,因?yàn)?src 和 copy 的 copy helper 是一樣的,所以用誰(shuí)的都行,調(diào)用的都是同一個(gè)函數(shù)
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            // 如果 src 沒(méi)有 copy/dispose helper
            // 將 Block_byref 后面的數(shù)據(jù)都拷貝到 copy 中,一定包括 Block_byref_3
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    // src 已經(jīng)在堆上,就只將引用計(jì)數(shù)加 1
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
  1. 如果 byref 原來(lái)在堆上,就將其拷貝到堆上,拷貝的包括 Block_byref、Block_byref_2Block_byref_3,被 __weak 修飾的 byref 會(huì)被修改 isa_NSConcreteWeakBlockVariable,原來(lái) byrefforwarding 也會(huì)指向堆上的 byref;
  2. 如果 byref 已經(jīng)在堆上,就只增加一個(gè)引用計(jì)數(shù)。

理解:
我們代碼的Block在聲明的時(shí)候一定是在棧上的,當(dāng)把Block拷貝到堆上時(shí)連同捕獲的局部變量一起拷貝(因?yàn)槎即鎯?chǔ)在結(jié)構(gòu)體__block_impl),如果此時(shí)使用__block修飾局部變量了,原本棧上Block__forwording指向棧Block自己,拷貝到堆上Block__forwording指向堆Block自己,而此時(shí)棧上的Block__forwording會(huì)改變指向堆上Block。

__block修飾的局部變量原本是在棧上的,需要拷貝到堆;__block修飾的局部變量原本是在堆上的,將其引用計(jì)數(shù)+1。

  • _Block_object_dispose的分析
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
// 當(dāng) block 和 byref 要 dispose 對(duì)象時(shí),它們的 dispose helper 會(huì)調(diào)用這個(gè)函數(shù)
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      // 如果是 byref
      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
        // 對(duì) byref 對(duì)象做 release 操作
        _Block_byref_release(object);
        break;
      // 如果是 block
      case BLOCK_FIELD_IS_BLOCK:
        // 對(duì) block 做 release 操作
        _Block_release(object);
        break;
      // 如果是對(duì)象
      case BLOCK_FIELD_IS_OBJECT:
        // 默認(rèn)啥也不干,但在 _Block_use_RR() 中可能會(huì)被 Objc runtime 或者 CoreFoundation 設(shè)置一個(gè) release 函數(shù),里面可能會(huì)涉及到 runtime 的引用計(jì)數(shù)
        _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修飾的外部局部變量,會(huì)走case BLOCK_FIELD_IS_BYREF: 調(diào)用_Block_byref_release函數(shù)(對(duì) byref 對(duì)象做 release 操作)

_Block_byref_release的源碼聲明:

// 對(duì) byref 對(duì)象做 release 操作,
// 堆上的 byref 需要 release,棧上的不需要 release,
// release 就是引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,就將 byref 對(duì)象銷(xiāo)毀
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 已經(jīng)被堆拷貝,則取得是堆上的 byref,否則是棧上的,棧上的不需要 release,也沒(méi)有引用計(jì)數(shù)
    byref = byref->forwarding;
    
    // byref 被拷貝到堆上,需要 release
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        // 取得引用計(jì)數(shù)
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        // 引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,會(huì)返回 true,表示 byref 需要被銷(xiāo)毀
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            // 如果 byref 有 dispose helper,就先調(diào)用它的 dispose helper
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                // dispose helper 藏在 Block_byref_2 里
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

對(duì) byref對(duì)象release 操作,堆上的 byref 需要 release,棧上的不需要 releaserelease 就是引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0,就將 byref 對(duì)象銷(xiāo)毀。

_Block_object_assign_Block_object_dispose是在Block捕獲外部局部變量時(shí)成對(duì)出現(xiàn),對(duì)捕獲的外部變量進(jìn)行內(nèi)存管理(引用計(jì)數(shù)增減)。

總結(jié)__block的底層原理:

__block修飾的變量在編譯過(guò)后會(huì)變成 __Block_byref__XXX 類(lèi)型的結(jié)構(gòu)體,在結(jié)構(gòu)體內(nèi)部有一 個(gè) __forwarding 的結(jié)構(gòu)體指針,指向結(jié)構(gòu)體本身。 block創(chuàng)建的時(shí)候是在棧上的,在將棧block拷?到堆上的時(shí)候,同時(shí)也會(huì)將block中捕獲的對(duì)象拷?到堆上,然后就會(huì)將棧上的__block修飾對(duì)象的__forwarding指針指向堆上的拷?之后的對(duì)象。 這樣我們?cè)?code>block內(nèi)部修改的時(shí)候雖然是修改堆上的對(duì)象的值,但是因?yàn)闂I系膶?duì)象的 __forwarding指針將堆和棧的對(duì)象鏈接起來(lái)。因此就可以達(dá)到修改的目的。

__block不可以用于修飾靜態(tài)變量和全局變量。

五、循環(huán)引用

  • 案例一:
- (void)test {
    self.block = ^{
        self.name = @"AnAn";
    };
}

原因:self -> block -> self。

block在聲明實(shí)現(xiàn)的時(shí)候就會(huì)捕獲外部局部變量,所以案例一不需要調(diào)用block也會(huì)產(chǎn)生循環(huán)引用。
即便把self.name改成_name,在捕獲的時(shí)候也會(huì)把self對(duì)象捕獲。

block要釋放必須self要釋放,可使用__weak去修飾self就解決循環(huán)引用:

- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        weakSelf.name = @"AnAn";
    };
}

但是依然會(huì)有問(wèn)題,比如block里面有一個(gè)dispatch_after

-(void)test {
    self.name = @"lg";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();
}

如果controller還沒(méi)等到執(zhí)行dispatch_afterpop/dismiss,等到3s后就會(huì)運(yùn)行dispatch_after,此時(shí)打印weakSelf.name的結(jié)果是null。
造成這樣的結(jié)果是controllerpop/dismiss后就釋放了。

解決方式一:強(qiáng)弱共舞(weak strong dance)

-(void)test {
    self.name = @"lg";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%p",&strongSelf);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
}

解決方式二:臨時(shí)變量

-(void)test1 {
    self.name = @"lg";
    __block MyViewController *vc = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();
}

解決方式三:block參數(shù)

-(void)test2 {
    self.name = @"lg";
    self.block = ^(MyViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);
}
  • 案例二:
static MyViewController *_staticSelf;
-(void)test2 {
    __weak typeof(self) weakSelf = self; // self是MyViewController的實(shí)例對(duì)象
    _staticSelf = weakSelf;
}
// 產(chǎn)生循環(huán)引用

__weak typeof(self) weakSelf = self;的意思是用一個(gè)弱引用指針指向self所指向的堆內(nèi)存,不會(huì)對(duì)堆內(nèi)存引用計(jì)數(shù)不會(huì)+1。
_staticSelf是不會(huì)自動(dòng)釋放的,所以self也是一直不被釋放。需要手動(dòng)把_staticSelf = nil

  • 案例三:
- (void)test3 {
    __weak typeof(self) weakSelf = self;
    self.block1 = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%p", &strongSelf);
        weakSelf.block2 = ^{
            NSLog(@"%@", strongSelf);
        };
        weakSelf.block2();
    };
    
    self.block1();
}
// 產(chǎn)生循環(huán)引用

weakSelf -> block2 -> StrongSelf
若把NSLog(@"%@", strongSelf); 換成 NSLog(@"%@", weakSelf);就不存在循環(huán)引用了,因?yàn)?code>block2捕獲的weakSelf是弱引用。

六、面試題

  • 第1道面試題
-(void)test {
    NSObject *objc = [NSObject new];
    NSLog(@"%ld", CFGetRetainCount((__bridge  CFTypeRef)objc));
    
    // 棧+1 堆+1
    void(^block1)(void) = ^{
        NSLog(@"%ld", CFGetRetainCount((__bridge  CFTypeRef)objc));
    };
    block1();
    
    // 棧+1
    void(^__weak block2)(void) = ^{
        NSLog(@"%ld", CFGetRetainCount((__bridge  CFTypeRef)objc));
    };
    block2();
    
     // 堆+1
    void(^block3)(void) = [block2 copy];
    block3();

    void(^block4)(void) = [block1 copy];
    block4();
}

// 13455

上面的分析已經(jīng)知道Block的本質(zhì)就是Block_layout,于是我仿照源碼寫(xiě)了一個(gè)_MyBlock結(jié)構(gòu)體:

// NSObject+Block.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void(*MyBlockCopyFunction)(void *, const void *);
typedef void(*MyBlockDisposeFunction)(const void *);
typedef void(*MyBlockInvokeFunction)(void *, ...);

enum {
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};

struct _MyBlockDescriptor1 {
    uintptr_t reserved;
    uintptr_t size;
};

struct _MyBlockDescriptor2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    MyBlockCopyFunction copy;
    MyBlockDisposeFunction dispose;
};

struct _MyBlockDescriptor3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
// 底層
struct _MyBlock {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // 函數(shù)指針
    MyBlockInvokeFunction invoke;
    struct _MyBlockDescriptor1 *descriptor;

};

static struct _MyBlockDescriptor3 * _My_Block_descriptor_3(struct _MyBlock *aBlock) {
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return nil;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct _MyBlockDescriptor1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct _MyBlockDescriptor2);
    }
    return (struct _MyBlockDescriptor3 *)desc;
}

static const char *MyBlockTypeEncodeString(id blockObj) {
    struct _MyBlock *block = (__bridge void *)blockObj;
    return _My_Block_descriptor_3(block)->signature;
}
@interface NSObject (Block)

// 打印Block 簽名
- (NSMethodSignature *)getBlcokSignature;

// 打印Block 簽名
- (NSString *)getBlcokSignatureString;

// 調(diào)用block
- (void)invokeBlock;

@end

NS_ASSUME_NONNULL_END
// NSObject+Block.m
#import "NSObject+Block.h"
@implementation NSObject (Block)

- (NSString *)getBlcokSignatureString {
    
    NSMethodSignature *signature = self.getBlcokSignature;
    if (signature) {
        NSMutableString *blockSignature = [NSMutableString stringWithFormat:@"BlcokSignature: return type: %s, ", [signature methodReturnType]];
        for (int i = 0; i < signature.numberOfArguments; i++) {
            [blockSignature appendFormat:@"argument number: %d, argument type: %s ", i+1, [signature getArgumentTypeAtIndex:i]];
        }
        return blockSignature;
    }
    return nil;
}

- (NSMethodSignature *)getBlcokSignature {
    if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:MyBlockTypeEncodeString(self)];
        return signature;
    }
    return nil;
}

- (void)invokeBlock {
    NSMethodSignature *signature = self.getBlcokSignature;
    if (signature) {
        // 動(dòng)態(tài)的消息轉(zhuǎn)發(fā)
        NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:signature];
        [blockInvocation invokeWithTarget:self];
    }
}
// block OC 對(duì)象
- (NSString *)description {
    if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
        //簽名
        return [NSString stringWithFormat:@"<%@:%p>--%@", self.class, self, [self getBlcokSignatureString]];
    }
    return [NSString stringWithFormat:@"<%@:%p>", self.class, self];
}
@end

于是我就可以對(duì)系統(tǒng)的Block進(jìn)行強(qiáng)轉(zhuǎn)成_MyBlock類(lèi)型去操作內(nèi)存:

  • 第2.1道面試題:
// ViewController.m
#import "ViewController.h"
#import "NSObject+Block.h"

@implementation ViewController

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
- (void)viewDidLoad {
    [super viewDidLoad];
    [self blockDemo1];
}

- (void)blockDemo1{
    int a = 1;
    // 棧block
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    // 強(qiáng)轉(zhuǎn)成自定義的_MyBlock結(jié)構(gòu)體類(lèi)型
    struct _MyBlock *blc = (__bridge struct _MyBlock *)weakBlock;
    void(^strongBlock)(void) = weakBlock;
    blc->invoke = nil;
    strongBlock();
}
@end

此時(shí)strongBlockweakBlock指向的是同一塊棧block內(nèi)存;
block的函數(shù)實(shí)現(xiàn)置為nil,會(huì)導(dǎo)致崩潰。

怎么修改不會(huì)導(dǎo)致崩潰?

void(^strongBlock)(void) = [weakBlock copy]; 

將棧block拷貝到堆,此時(shí)strongBlock是指向堆內(nèi)存,weakBlock是指向棧內(nèi)存。blc->invoke = nil;是修改棧block的內(nèi)容,并不影響strongBlock的調(diào)用。

  • 第2.2道面試題:
#import "ViewController.h"
#import "NSObject+Block.h"
@interface ViewController ()

@end

@implementation ViewController

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self blockDemo2];
}

- (void)blockDemo2 {
    int a = 10;
    void(^__weak block1)(void) = nil;
    // {}里的代碼塊會(huì)立馬執(zhí)行
    {
        // 棧block2
        void(^__weak block2)(void) = ^{
            NSLog(@"%d",a);
        };
        block1 = block2;

        int b = 5;
        NSLog(@"%p", &c);
    }
    // 此時(shí)出了{(lán)...}的作用域b會(huì)被釋放嗎?
    block1();  // 10
}
@end

此時(shí)的block1();會(huì)打印10。

此時(shí)出了{...}的作用域b會(huì)被釋放嗎? 不會(huì)。
因?yàn)閧}的一整塊代碼需要出了blockDemo2方法作用域才會(huì)被釋放。

  • 第2.3道面試題:

此時(shí)我把blockDemo2再修改一下

- (void)blockDemo3{
    int a = 10;
    void(^__weak block1)(void) = nil;
    {
        // 棧block
        void(^__weak block2)(void) = ^{
            NSLog(@"%d",a);
        };
        block1 = [block2 copy]; 
    }
    block1();
}

此時(shí)block1();會(huì)崩潰!為什么?
因?yàn)?code>[block2 copy];是返回的結(jié)果是堆內(nèi)存的block,同時(shí)block1去弱引用這塊堆內(nèi)存并不會(huì)發(fā)生引用計(jì)數(shù)+1的情況(堆的內(nèi)存管理是通過(guò)引用計(jì)數(shù)的),所以block1在出{...}作用域的時(shí)候,ARC會(huì)自動(dòng)地[block1 release],導(dǎo)致block1被釋放。

要解決上面的問(wèn)題,需要一個(gè)強(qiáng)引用的方式去引用[block2 copy];

 void(^block1)(void) = nil;
  • 第2.4道面試題:
    此時(shí)我再把blockDemo2再修改一下
- (void)blockDemo4 {
    NSObject *objc = [NSObject new];
    void(^__weak block1)(void) = nil;
    {
        void(^__weak block2)(void) = ^{
            NSLog(@"%@", objc);
        };
        block1 = block2;
        block1();
    }
    block1();
}

{...}里的block1()會(huì)打印objc的地址,block1會(huì)打印null

<NSObject:0x600000060590>
(null)

因?yàn)樵?code>{...}里棧block2捕獲的objc是在堆區(qū),并且objc的引用計(jì)數(shù)不會(huì)改變,在出{...}objcARC自動(dòng)釋放[objc release]。

blockDemo4方法進(jìn)行如下修改

- (void)blockDemo4{
    NSObject *objc = [NSObject new];
    void(^block1)(void) = nil;
    {
        void(^__weak block2)(void) = ^{
            NSLog(@"%@", objc);
        };
        block1 = [block2 copy];
        block1();
    }
    block1();
}
Block與內(nèi)存布局相關(guān)面試題

面試題:Block中可以修改全局變量,全局靜態(tài)變量,局部靜態(tài)變量,局部變量 嗎?

  • 可以修改全局變量,全局靜態(tài)變量。因?yàn)槿肿兞?和 靜態(tài)全局變量是全局的,作用域很廣。

  • 可以修改局部靜態(tài)變量,不可以修改局部斌量。
    1.局部靜態(tài)變量(static修飾的) 和 局部變量,被block從外面捕獲,成為 __main_block_impl_0這個(gè)結(jié)構(gòu)體的成員變量;
    2.局部靜態(tài)變量是以指針形式被block捕獲的,由于捕獲的是指針,所以可以修改局部靜態(tài)變量的值;
    3.局部變量是以值方式傳遞到block的構(gòu)造函數(shù),只會(huì)捕獲block中會(huì)用到的變量,由于只捕獲了變量的值,并非內(nèi)存地址,所以在block內(nèi)部不能改變局部變量的值。

  • ARC環(huán)境下,一旦使用__block修飾并在block中修改,就會(huì)觸發(fā)copy,block就會(huì)從棧區(qū)copy到堆區(qū),此時(shí)的block是堆區(qū)block。

  • ARC模式下
    1.block中引用id類(lèi)型的數(shù)據(jù),無(wú)論有沒(méi)有__block修飾,都會(huì)retain。
    2.block中引用基礎(chǔ)數(shù)據(jù)類(lèi)型的數(shù)據(jù),沒(méi)有__block就無(wú)法修改變量值;如果有__block修飾,也是在底層修改__Block_byref_a_0結(jié)構(gòu)體,將其內(nèi)部的forwarding指針指向copy后的地址,來(lái)達(dá)到值的修改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容