iOS探索 全方位解讀Block

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)c++

前言

今天遇到一道方法交換面試題跟你們分享下...我仍是太年輕了面試

相信你們對本文的主角block都有必定的瞭解,平常開發中也常常能看到它的身影。本文會從block概念blcok循環引用block底層三方面進行講解bash

1、初識block

1.block定義

帶有自動變量(局部變量)的匿名函數叫作block,又叫作匿名函數代碼塊多線程

在不一樣語言中的叫法不一樣app

程序語言 Block的名稱
C Block
Smalltalk Block
Ruby Block
Python Lambda
C++ Lambda
JS Anonymous function

2.block分類

  • 全局block——__NSGlobalBlock__
void (^block)(void) = ^{
    NSLog(@"111");
};
NSLog(@"%@", block);
--------------------輸出結果:-------------------
<__NSGlobalBlock__: 0x10a870050>
--------------------輸出結果:-------------------
複製代碼
  • 堆block——__NSMallocBlock__
int a = 0;
void (^block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);
--------------------輸出結果:-------------------
<__NSMallocBlock__: 0x600002dca2b0>
--------------------輸出結果:-------------------
複製代碼
  • 棧block——__NSStackBlock__
int a = 0;
NSLog(@"%@", ^{
    NSLog(@"%d", a);
});
--------------------輸出結果:-------------------
<__NSStackBlock__: 0x7ffeec41e1b8>
--------------------輸出結果:-------------------
複製代碼

總結:框架

  • 不使用外界變量的block是__NSGlobalBlock__類型
  • 使用外界變量的block是__NSMallocBlock__類型
  • 在堆block拷貝前的block是__NSStackBlock__類型

除此以外,還有三種系統級別的block類型(能在libclosure源碼中看到)iphone

  • _NSConcreteAutoBlock
  • _NSConcreteFinalizingBlock
  • _NSConcreteWeakBlockVariable

2、block循環引用

1.循環引用的分析

這段代碼就是傳說中的 循環引用,與此同時編譯器也發出了警告

Capturing 'self' strongly in this block is likely to lead to a retain cycle
複製代碼

那麼就來分析一下循環引用的問題所在:函數

  • self持有了block
  • block持有了self(self.name)

這樣就造成了self -> block -> self的循環引用佈局

接下來不得不提到內存管理問題了(A引用B)post

  • 正常釋放時:A發送dealloc信號讓Bdealloc

  • 循環引用時:A、B互相引用,引用計數不能爲0,dealloc不會被調用

接下來就介紹一下解決循環引用的幾種辦法

2.循環引用的解決方法

2.1 強弱共舞
__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    NSLog(@"%@", weakSelf.name);
};
複製代碼

使用 中介者模式 __weak typeof(self) weakSelf = self將循環引用改成weakself -> self -> block -> weakself

表面看上去仍是一個「引用圈」,可是weakself -> self這一層是弱引用——引用計數不處理,使用weak表管理。因此此時在頁面析構時self就能正常的調用dealloc

但並非最終的解決方案,此時仍存在着問題

__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", weakSelf.name);
    });
};
複製代碼

如同這種延時狀況,如若調用block以後立馬返回上一頁進行頁面釋放,3秒後weakself指向的self已經爲nil了,此時的打印就只能打印出null

因而就有了強持有這麼一說法

__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", strongSelf.name);
    });
};
複製代碼

再加一層臨時的強持有,此時的引用就變成了strongself -> weakself -> self -> block -> strongself

看上去又是一個循環引用,但實際上strongSelf是個臨時變量,當block做用域結束後就會釋放,從而打破循環引用進行釋放(讓釋放延後了3秒)

2.2 其餘中間者模式

既然有「自動置空」,那麼也能夠「手動置空」

__block ViewController *vc = self;
self.name = @"Felix";
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    });
};
複製代碼

上述代碼也是使用 中介者模式 打破循環應用的——使用vc做爲中介者代替self從而打破循環引用

此時的引用狀況爲vc -> self -> block -> vc (vc在用完以後手動置空)

可是隻要不調用block,仍然存在着循環應用

解決循環引用還有一種方式——不引用

self.name = @"Felix";
self.block = ^(ViewController *vc) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    });
}
複製代碼

上述代碼使用當前vc做爲參數傳入block時拷貝一份,就不會出現持有的狀況,同時還能使用self的內存空間,可以完美避免循環引用

3.循環引用的補充說明

  • Masonry中是否存在循環引用?

Monsary使用的block是當作參數傳遞的,即使block內部持有self,設置佈局的view持有block,可是block不持有view,當block執行完後就釋放了,self的引用計數-1,因此block也不會持有self,因此不會致使循環引用

  • [UIView animateWithDuration: animations:]中是否存在循環引用?

UIView動畫是類方法,不被self持有(即self持有了view,但view沒有實例化)因此不會循環引用

  • 使用Facebook的開源框架能檢測是否存在循環引用
- (void)checkLeaks {
    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
    [detector addCandidate:self];
    NSSet *retainCycles = [detector findRetainCycles];
    NSLog(@"%@", retainCycles);
}
複製代碼

3、block底層

1.block本質

int main(){
    __block int a = 10;
    void(^block)(void) = ^{
        a++;
        printf("Felix - %d",a);
    };
    block();
    return 0;
}
複製代碼

clang將上述代碼輸出成cpp文件來查看底層實現

clang -rewrite-objc main.c -o main.cpp
複製代碼
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;
}
複製代碼
  1. main函數中能夠看到block的賦值是__main_block_impl_0類型,它是C++中的構造函數

全局搜索__main_block_impl_0能看到它的定義

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的本質是個 __main_block_impl_0 的結構體對象,這就是爲何能用 %@ 打印出block的緣由了

  1. 再來看看析構函數中所須要的函數:fp傳遞了具體的block實現__main_block_func_0,而後保存在block結構體的impl

這就說明了block聲明只是將block實現保存起來,具體的函數實現須要自行調用

  1. 當block爲堆block時(外接傳入變量)從新clang編譯
int main(){
    
    int a = 10;
    void(^block)(void) = ^{
        printf("Felix %d ", a);
    };
    
    block();
    return 0;
}
複製代碼

此時的block構造函數中就會多出一個參數a,而且在block結構體中也會多出一個屬性a

接着把目光轉向__main_block_func_0實現

  • __cself__main_block_impl_0的指針,即block自己
  • int a = __cself->aint a = block->a
  • 因爲a只是個屬性,因此是堆block只是值拷貝(值相同,內存地址不一樣)
  • 這也是爲何捕獲的外界變量不能直接進行操做的緣由,如a++會報錯
  1. 當__block修飾外界變量時
int main(){
    
    int a = 10;
    void(^block)(void) = ^{
        printf("Felix %d ", a);
    };
    
    block();
    return 0;
}
複製代碼

__block修飾的屬性在底層會生成響應的結構體,保存原始變量的指針,並傳遞一個指針地址給block——所以是指針拷貝

2.block簽名

接下來就來到libclosure源碼中仔細看一看瞧一瞧

首先來看block結構體對象Block_layout(等同於clang編譯出來的__Block_byref_a_0

#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_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
};
複製代碼

其中Block_layout是基礎的block結構空間,而部分block則擁有Block_descriptor_2Block_descriptor_3結構,其中的flags標識記錄了一些信息

  • 第1位:釋放標記,通常經常使用BLOCK_NEEDS_FREE作位與操做,一同傳入flags,告知該block可釋放
  • 第16位:存儲引用計數的值,是一個可選參數
  • 第24位:第16位是否有效的標誌,程序根據它來決定是否增長火箭少女引用計數位的值
  • 第25位:是否擁有拷貝輔助函數
  • 第26位:是否擁有block析構函數
  • 第27位:標誌是否有垃圾回收
  • 第28位:標誌是不是全局block
  • 第30位:與BLOCK_USE_START相對,判斷當前block是否擁有一個簽名,用於runtime時動態調用

但部分block則擁有Block_descriptor_2Block_descriptor_3結構這句話又該怎麼去理解呢?請看下面的解釋

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

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;
}
複製代碼
  • 若是aBlock->flags & BLOCK_HAS_COPY_DISPOSE知足,則_Block_descriptor_2存在,反之則block沒有_Block_descriptor_2這個結構
    • _Block_descriptor_2能夠經過Block_descriptor_1內存偏移獲得
  • 同理,aBlock->flags & BLOCK_HAS_SIGNATURE知足,則_Block_descriptor_3存在
    • _Block_descriptor_3能夠經過Block_descriptor_2內存偏移獲得

決定這兩個結構是否存在的絕對因素其實就是Block_layoutflags

// Values for Block_layout->flags to describe block objects
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中的簽名

  • __NSGlobalBlock__簽名(_Block_copy進入時)

  • __NSStackBlock__簽名(_Block_copy進入時)

  • __NSMallocBlock__簽名(_Block_copy返回時)

最後經過內存平移也拿到了block的簽名,能夠看出

  • 這個block的返回值爲空值
  • block的簽名爲@?,表明着不明對象

3.block的copy分析

接下來就來研究下棧block轉換成到堆block的過程——_Block_copy

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;
    }
}
複製代碼

整段代碼主要分紅三個邏輯分支

  1. 經過flags標識位——存儲引用計數的值是否有效

block的引用計數不受runtime處理的,是由本身管理的

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;
        }
    }
}
複製代碼

這裏可能有個疑問——爲何引用計數是 +2 而不是 +1

——由於flags的第一號位置已經存儲着釋放標記

  1. 是不是全局block——是的話直接返回block

  2. 棧block -> 堆block的過程

  • 先經過malloc在堆區開闢一片空間
  • 再經過memmove將數據從棧區拷貝到堆區
  • invokeflags同時進行修改
  • block的isa標記成_NSConcreteMallocBlock

4.__block的深刻探究

爲了更好地進行探究,咱們在OCmain文件中進行clang編譯

xcrun -sdk iphonesimulator clang -rewrite-objc main.m
複製代碼
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        __block NSString *name = [NSString stringWithFormat:@"Felix"];
        void (^fxBlock)(void) = ^{ // block_copy
            name = @"Feng Felix";
        };
        fxBlock();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
複製代碼
4.1 第一層拷貝(block)

block中的第一層拷貝其實已經講過了——_Block_copy將block從棧拷貝到堆

4.2 第二層拷貝(捕獲變量的內存空間)

在函數聲明時會傳 __main_block_desc_0_DATA結構體,在裏面又會去調用 __main_block_copy_0函數, __main_block_copy_0裏面會調用 _Block_object_assign——這就是第二層拷貝的調用入口

接下來就來看看_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進到不一樣分支來處理捕獲到的變量

枚舉值 數值 含義
BLOCK_FIELD_IS_OBJECT 3 對象
BLOCK_FIELD_IS_BLOCK 7 block變量
BLOCK_FIELD_IS_BYREF 8 __block修飾的結構體
BLOCK_FIELD_IS_WEAK 16 __weak修飾的變量
BLOCK_BYREF_CALLER 128 處理block_byref內部對象內存的時候
會加的一個額外的標記,配合上面的枚舉一塊兒使用

此時捕獲到的變量是被__block修飾的BLOCK_FIELD_IS_BYREF類型,就會調用*dest = _Block_byref_copy(object);

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
        // 用原目標的大小在堆區生成一個Block_byref
        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;
        
        // 原來的區域和新的區域都指向同一個對象,使得block具有了修改能力
        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;
}
複製代碼
  • 用原目標name的大小在堆區生成一個Block_byref
  • copy->forwarding = copy; & src->forwarding = copy;——原來的區域和新的區域都指向同一個對象,使得block具有了修改能力
  • (*src2->byref_keep)(copy, src)開始第三層拷貝
4.3 第三層拷貝(拷貝對象)

(*src2->byref_keep)(copy, src)跟進去會來到Block_byref結構來,而byref_keepBlock_byref的第5個屬性

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

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

複製代碼

__block修飾的變量在底層其實調用了以下的構造方法——此時的第5位就等於byref_keep,因此在第二層拷貝時會調用__Block_byref_id_object_copy_131

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製代碼

第五個函數會去調用_Block_object_assign函數

這個(char*)dst + 40看着以爲好莫名其妙啊...其實看到__Block_byref_name_0就頓悟了,恰好取得變量name對象

struct __Block_byref_name_0 {
  void *__isa;
__Block_byref_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *name;
};
複製代碼

_Block_object_assign在對BLOCK_FIELD_IS_OBJECT狀況時會作出以下操做:

case BLOCK_FIELD_IS_OBJECT:
    /******* id object = ...; [^{ object; } copy]; ********/

    _Block_retain_object(object);
    *dest = object;
    break;
複製代碼
  • _Block_retain_object是個空函數,由於block捕獲的外接變量由ARC自動管理
  • 捕獲到name進行拷貝
4.4 _Block_object_dispose

看完了三層拷貝,再來看一下釋放函數_Block_object_dispose

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

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);
        }
    }
}
複製代碼
  • 若是是釋放對象就什麼也不作(自動釋放)
  • 若是是__block修飾,就將指向指回原來的區域並使用free釋放

5.總結

  • block的本質是個__main_block_impl_0的結構體對象,因此能用%@打印
  • block聲明只是將block實現保存起來,具體的函數實現須要自行調用
  • block捕獲外界變量時block結構體會自動生成一個屬性來保存變量
  • __block修飾的屬性在底層會生成響應的結構體,保存原始變量的指針,並傳遞一個指針地址給block
  • block中有三層拷貝:拷貝block、拷貝捕獲變量的內存地址、拷貝對象

寫在後面

block還有hook一塊也是須要去學習瞭解的

小小的block也是有不少底層知識須要研究的,越學會發現本身越眇小,其實否則,只是你的視角開闊了,正是如此纔會進步

相關文章
相關標籤/搜索