Block 淺析

1.Block 的定義

In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

意思就是,在編程語言中,閉包是一個引用外部變量(有時候也稱作自由變量)的,函數(或指向函數的指針)。

Objective-C 中的 block 其實就是對于閉包的實現。

2.Block 的結構

block 的數據結構定義如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

通過該數據結構,我們可以知道,一個 block 實例實際上由 6 部分構成:

  1. isa 指針,所有對象都有該指針,用于實現對象相關的功能。
  2. flags,用于按 bit 位表示一些 block 的附加信息, block copy 的實現代碼可以看到對該變量的使用。
  3. reserved,保留變量。
  4. invoke,函數指針,指向具體的 block 實現的函數調用地址。
  5. descriptor, 表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。
  6. variables,capture 過來的變量,block 能夠訪問它外部的局部變量,就是因為將這些變量(或變量的地址)復制到了結構體中。

Objective-C中,有3種 block 類型:

  1. _NSConcreteGlobalBlock 全局的靜態 block。
  2. _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。
  3. _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀。

3 種 block 示例如下:

typedef void (^Block)(id info);
static NSString * const kTestDemo = @"TestDemo";

- (void)blockTest1 {
    
    int i = 0;
    Block block1 = ^(id info) {
        NSLog(@"Block1 %d", i);
    };
    
    NSLog(@"%@", ^{NSLog(@"Hello, world!");});                  //__NSGlobalBlock__
    NSLog(@"%@", ^{NSLog(@"%@.Hello, world!", kTestDemo);});    //__NSGlobalBlock__
    NSLog(@"%@", ^{NSLog(@"%d.Hello, world!", i);});            //__NSStackBlock__
    NSLog(@"%@", [^{NSLog(@"%d.Hello, world!", i);} copy]);     //__NSMallocBlock__
    NSLog(@"%@", block1);                                       //__NSMallocBlock__

}


然后,我們先來研究下 NSGlobalBlock 的底層實現,先新建一個名為 block.c 的源文件,然后用

clang -rewrite-objc block.c

將其轉化為 C++ 實現,生成 block.cpp 文件。

block.c 源代碼如下:

#include <stdio.h>
int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

block.cpp 文件中的關鍵代碼如下:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Hello, World!\n");
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };

int main()
{
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
    return 0;
}

從中我們可以看出,__main_block_impl_0 就是 block 的實現,從 block 的結構體我們可以看出:

  1. 它主要由一個 impl 、 一個 descriptor 和一個構造函數 __main_block_impl_0 組成。
  2. 該結構體的前兩個參數也都是結構體,第二個參數Desc中,是關于結構體的大小、版本升級所需保留參數,暫不做過多解析。
  3. 第一個結構體參數的結構體中,
    1. 看到了 isa 指針,說明 block 也是 Objective-C 中的對象,isa 指向對象的一個 8 字節;
    2. Flags 和 Reserved 是某些標記字段,暫不過多解釋;
    3. FuncPtr 函數指針,其實就是block所需執行的代碼段,存放的是地址
  4. 結構體的構造函數中,
    1. 傳入的第一個參數,就是函數指針,impl.FuncPtr = fp;
    2. block 的 isa 指向 _NSConcreteStackBlock 指針,也就是說,當一個 block 被聲明的時候,它都是一個 _NSConcreteStackBlock 類的對象;
    3. 可以看出,構造函數是對block的初始化
  5. __main_block_impl_0 函數中其實存儲著我們block中寫下的代碼。

block 的底層數據結構可以用下面一張圖來展示:

image

我們再來看 main() 函數實現,把其中的類型都去掉,如下

/* 調用結構體函數初始化 
 __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);

由此,可以看出,__main_block_func_0 函數的地址和 __main_block_desc_0_DATA (也就是 { 0, sizeof(struct __main_block_impl_0) } )的地址,傳入到了 __main_block_impl_0 的構造函數里,被保存到block的結構體中。

3.Block 的變量獲取

1) 獲取局部變量的 block 結構體

首先,用 clang 命令將 Objective-C 代碼轉換為 C++ 實現,其中 Objective-C 源碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 0;
        static int b = 1;
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
    }
    return 0;
}

//打印結果為:a = 0; b = 3;

轉換的 C++ 實現如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //值
  int *b; //指針
  
  //1.
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//2.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_e0fd9d_mi_0, a, (*b));
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 0;
        static int b = 1;
        
        //3.
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));

        a = 2;
        b = 3;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

從上面可以看出,__main_block_impl_0 結構體中多了兩個變量,用來保存block獲取的外部的變量,其中 a 是 int 類型,b 是 int * 類型,也就是 a 保存的是變量的值,b 保存的是變量的指針;從 3. 處代碼就可以看出 b 靜態局部變量傳入的是變量的地址。

2)獲取全局變量的 block 結構體

同樣,用 clang 命令將 Objective-C 代碼轉換為 C++ 實現,其中 Objective-C 源碼如下:

int a = 0;
static int b = 1;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
    }
    return 0;
}

//打印結果為:a = 2; b = 3;

轉換的 C++ 實現如下:

int a = 0;
static int b = 1;


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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_eaada3_mi_0, a, b);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        a = 2;
        b = 3;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

由上可以看出,block 根本就沒有 capture 全局變量保存到自己的結構體中,而是直接調用并賦值,這是因為局部變量因為跨函數訪問所以需要捕獲,全局變量在哪里都可以訪問,所以不用捕獲。

總結:

變量類型 捕獲到 block 內部 訪問方式
局部變量 ? 值傳遞
靜態局部變量 ? 指針傳遞
全局變量 ? 直接訪問

3)block 是怎么獲取捕獲的 self 的

以下代碼中 block 是否會捕獲變量呢?

#import "Person.h"

@interface Person ()

@property (nonatomic, copy) NSString *name;

@end

@implementation Person

- (instancetype)initWithName:(NSString *)name {
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

- (void)test1 {
    void(^block)(void) = ^{
        NSLog(@"實例方法:%@",self);
    };
    block();
}

+ (void)test2 {
    NSLog(@"類方法.");
}

@end

同樣,轉換為 C++ 代碼查看其內部結構

static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) {
    if (self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
}


struct __Person__test1_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test1_block_desc_0* Desc;
  Person *self; //捕獲到self指針
  __Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy  取出block中 self 的值

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_0,self); //使用上面取到的 self 的值
    }
    
static void __Person__test1_block_copy_0(struct __Person__test1_block_impl_0*dst, struct __Person__test1_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test1_block_dispose_0(struct __Person__test1_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test1_block_impl_0*, struct __Person__test1_block_impl_0*);
  void (*dispose)(struct __Person__test1_block_impl_0*);
} __Person__test1_block_desc_0_DATA = { 0, sizeof(struct __Person__test1_block_impl_0), __Person__test1_block_copy_0, __Person__test1_block_dispose_0};

static void _I_Person_test1(Person * self, SEL _cmd) { //test1實例方法默認傳入兩個參數,一個是 self ,一個是 _cmd (函數具體實現的函數指針)
    void(*block)(void) = ((void (*)())&__Person__test1_block_impl_0((void *)__Person__test1_block_func_0, &__Person__test1_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

static void _C_Person_test2(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_1);
}

從上面可以看出,不論對象方法還是類方法都會默認將 self 作為參數傳遞給方法內部,既然是作為參數傳入,那么 self 肯定是局部變量。就如上面得到的結論局部變量肯定會被 block 捕獲。

那么,接下來我們來看一下如果在 block 中使用成員變量或者調用實例的屬性會有什么不同的結果。

- (void)test1 {
    void(^block)(void) = ^{
        NSLog(@"%@",self.name);
        NSLog(@"%@",self->_name);
    };
    block();
}
struct __Person__test1_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test1_block_desc_0* Desc;
  Person *self;//這里同樣只是捕獲了self
  __Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));//屬性這里是調用 objc_msgSend 發送消息
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_1,(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)));//成員變量這里是直接訪問內存
    }

3)block 的類型

從上面的 C++ 實例代碼中發現,block 構造函數中的 isa 都是指向
_NSConcreteStackBlock 類對象地址,而在 Objective-C 中打印,能得出三種類型的 block ,這是為什么呢?

首先,我們先來看一下 block 的類型打印

int a = 0;
static int b = 1;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
        NSLog(@"%@", [Block superclass]);
        NSLog(@"%@", [[Block superclass] superclass]);
        NSLog(@"%@", [[[Block superclass] superclass] superclass]);
        NSLog(@"%@", [[[[Block superclass] superclass] superclass] superclass]);
        NSLog(@"%@", [[[[Block superclass] superclass] superclass] class]); // NSObject
        
    }
    return 0;
}

//打印結果如下:
//__NSGlobalBlock
//NSBlock
//NSObject
//(null)
//NSObject

從上,可以看出 block 本質其實就是 Objective-C 對象。

block 的類型又是如何定義呢?如下圖所示:

block類型 環境 內存區域
NSGlobalBlock 沒有訪問 auto 變量 數據段
NSStackBlock 訪問了auto變量
NSMallocBlock NSStackBLock 調用了copy

經分析可知,ARC 條件下,編譯器會根據情況自動將棧上的 block 進行一次 copy 操作,將 block 復制到堆上,那么什么情況下 ARC 會自動將 block 進行一次 copy 操作呢?

  1. block 作為函數返回值時;
typedef void (^Block)(void);
Block myblock() {
    int a = 10;
    // 上文提到過,block中訪問了auto變量,此時block類型應為__NSStackBlock__
    Block block = ^{
        NSLog(@"---------%d", a);
    };
    return block;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
       // 打印block類型為 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
}
  1. 將 block 賦值給 __strong 指針時;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // block內沒有訪問auto變量
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"%@",[block class]);
        
        int a = 10;
        // block內訪問了auto變量,但沒有賦值給__strong指針
        NSLog(@"%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        
        // block賦值給__strong指針
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@",[block1 class]);
    }
    return 0;
}

//打印如下:
// __NSGlobalBlock__
// __NSStackBlock__
// __NSMallocBlock__
  1. block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數時;
例如:遍歷數組的 block 方法,將 block 作為參數的時候。

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"array = %@, idx = %lu, value = %@", array, (unsigned long)idx, obj);

}];
  1. block 作為 GCD API 的方法參數時;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    self = [[YYFileManager alloc] init];
});        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
});

那么又回到上面的問題,為什么 C++ 中指向的都是 _NSConcreteStackBlock 呢?大膽的猜測一下,其是不是在 runtime 運行時過程中對 block 類型進行了轉變,最終 block 類型以 runtime 運行時類型,也就是我們打印出的類型為準。

來看一下 block 被 copy 的示例代碼:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1.
    if (!arg) return NULL;
    // 2.
    aBlock = (struct Block_layout *)arg;
    // 3.
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4.
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5.
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6.
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7.
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8.
    result->isa = _NSConcreteMallocBlock;
    // 9.
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

從中可以看出,在第 8 步,目標的 block 類型被修改為 _NSConcreteMallocBlock。(代碼來自這里<html>http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy</html>)

那 block 怎么釋放呢,這里分 6 個步驟:

void _Block_release(void *arg) {
    // 1.
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    
    // 2.
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
    
    // 3.
    if (newCount > 0) return;
    
    // 4.
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }
    
    // 5.
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }
    
    // 6.
    else {
        printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock);
    }
}

4)__block 實現原理

我們都知道,要想在 block 內修改外部基礎變量(float、int、long等),需要在前面加修飾符 __block ,那么它是如何實現修改呢,我們來看源碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int a = 0;
        NSLog(@"Block 外:a = %d , &a = %p", a, &a);

        NSLog(@"Block 外:a = %d , &a = %p", a, &a);
        void (^Block)(void) = ^{
            a = 1;
            NSLog(@"Block 內:a = %d , &a = %p", a, &a);
        };
        
        a = 2;
        
        Block();
        
    }
    return 0;
}

//打印結果:
//Block 外:a = 0 , &a = 0x7ffeefbff508
//Block 內:a = 1 , &a = 0x10072c0a8

從打印結果看出,a 的地址變了,而且變化挺大的,從地址和上面分析可以看出,a 變量從棧區被 copy 了一份到堆區,我們再來看看 C++ 源碼:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 1;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_bb7d34_mi_0, (a->__forwarding->a));
        }
        
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};

        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

        (a.__forwarding->a) = 2;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

1)從第一個結構體 struct __Block_byref_a_0 可以看出,__block 修飾的變量 a 被轉化為了一個結構體,這個結構體有5個成員變量:

  1. isa 指針,說明它由基礎變量變成了OC對象
  2. forwarding 指針,這個后面再詳細解釋
  3. flags,標記用的
  4. size,該結構體的大小
  5. a,變量值

2)從構造函數 __main_block_func_0 中 (a->__forwarding->a) = 1 和 mian 函數中 (a->__forwarding->a) = 2 可以看出,棧區的變量 a 的 __forwarding 指針指向堆區的 a 變量的結構體,而堆中 a 變量的結構體的 __forwrding 指針指向自己,如圖:


image

而在 MRC 中,即便 blcok 捕獲了__block 變量,也不會從棧上 copy 到堆上,需要自己手動處理,那么這時候變量結構體的 __forwarding 指向自己,如圖:


image

如果,__block 修飾 OC 對象呢,它會是什么樣的呢?源碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
            
        __block id block_obj = [[NSObject alloc]init];
        id obj = [[NSObject alloc]init];
        
        NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        
        void (^myBlock)(void) = ^{
            NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        };
        
        myBlock();
    }
    return 0;
}

//打印結果:

//block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]

//Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]

編譯后的 C++ 代碼如下:

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  id obj = __cself->obj; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
        id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);

        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

首先,OC 中,ARC 默認聲明自帶 __strong 所有權修飾符,所以 main 函數的默認聲明如如下:

__block id __strong block_obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];

綜上,可以看出ARC環境下,block 捕獲外部對象變量,是會 copy 一份的,可以看出他們的地址不同。block 內會保留強引用修飾的對象,如果不做處理,就會產生循環引用。


參考文獻:

clang命令相關:https://cotin.tech/iOS/clang-rewrite-objc/

Block底層原理:https://juejin.im/post/5b0181e15188254270643e88

__block實現原理:http://www.lxweimin.com/p/ee9756f3d5f6

談Objective-C block的實現:https://blog.devtang.com/2013/07/28/a-look-inside-blocks/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容