前言

一、Block的类型

二、block解决循环引用的方式

三、block捕获变量

四、block修改局部变量

五、block的内存变化分析

六、block的底层结构

七、Block_copy分析

八、block捕获外界变量时的三层拷贝

九、问题补充

十、流程总结


今天来具体的总结一下Block的相关知识点。因为Block在日常的开发中会经常使用Block,所以很有必要了解它的底层实现原理。


一、Block的类型

1、NSGlobalBlock

- (void)test1 {
    void(^block)(void) = ^{
    };
    NSLog(@"%@",block);
}

此时打印出来的block,就是__NSGlobalBlock__,此时block在内存的全局区。

2、NSMallocBlock

- (void)test2 {
    int a = 1;
    void(^block)(void) = ^{
        NSLog(@"%i",a);
    };
    NSLog(@"%@",block);
}

当block捕获外界变量时,此时打印为__NSMallocBlock__,此时block在内存的堆区。

3、NSStackBlock

在早期的版本,当block捕获外界变量,且没有对block进行copy的时候,是NSStackBlock类型。但是目前的版本系统进行了一次copy,所以栈的block基本在应用层出现的很少。

- (void)test3 {
    int a = 1;
    void(^ __weak block)(void) = ^{
        NSLog(@"%i",a);
    };
    NSLog(@"%@",block);
}

将block去掉底层的强持有,此时打印就为__NSStackBlock__,此时block在内存的栈区。

二、block解决循环引用的方式

说到循环引用,其实就是你中有我,我中有你。正常的释放流程如下:

循环引用的流程如下:

打破循环引用方式

1、使用weak(弱引用)

- (void)test1 {
    self.name = @"1";
    __weak typeof(self)weakSelf = self;
    self.block = ^(void) {
        NSLog(@"%@",weakSelf.name);
    };
    self.block();
}
- (void)dealloc {
    NSLog(@"释放");
}

以上demo的引用情况如下:
self->block->weakSelf->self
self引用了block,block引用了wealSelf(被block捕获的变量是__weak),weakSelf引用了self(底层的weak表),没有对self的引用计数+1。
所以block->self这一层的持有就被打破。也就解决了循环引用。
继续看下面的demo:

- (void)test1 {
    self.name = @"1";
    __weak typeof(self)weakSelf = self;
    self.block = ^(void) {
        //模拟网络请求或其他的异步任务
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(2);
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();
}

以上dealloc确实走了,但是打印的name为null,因为self已经被释放了。所以这里self的生命周期是不足以得到保证的,此时应该在用一个临时变量强引用一下weakself,等待异步任务执行完之后释放。

- (void)test1 {
    self.name = @"1";
    __weak typeof(self)weakSelf = self;
    self.block = ^(void) {
        __strong typeof(weakSelf)strongSelf = weakSelf;
        //模拟网络请求或其他的异步任务
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
}

以上strongSelf是一个临时变量,被gcd所持有,当gcd的异步任务执行完毕之后,临时变量释放,self的引用计数-1,也就释放了。

总结:使用 weak-strong 来解决循环引用。

2、利用临时变量(手动管理)

- (void)test2 {
    self.name = @"test";
    //self->block->vc->self
    __block Test2ViewController *vc = self;
    self.block = ^(void) {
        NSLog(@"%@",vc.name);
        vc = nil;
    };
    self.block();
}

以上,因为加了__block之后,block内对vc进行了持有,所以临时变量vc不会因为出作用域而释放。另外因为block内部持有的是地址指针(下面会具体分析),并没有对self本身引用计数+1,所以当调用self.block()时,将vc=nil,也就使本身self引用计数-1,从而消除了循环引用。

3、将self做为block的参数

- (void)test3 {
    self.name = @"test";
    self.block2 = ^(Test2ViewController *vc) {
        NSLog(@"%@",vc.name);
    };
    self.block2(self);
}

以上将self作为block的参数传入,block内会当作临时变量处理,不会对它持有。从而解决循环引用。

总结,以上是3中解决block循环引用的方法,第一种用的最多。

三、block捕获变量

1、通过xcrun编译以下代码:

//xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        void(^block)(void) = ^{
            NSLog(@"test");
        };
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

编译之后的cpp整理如下:

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

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, char * argv[]) {
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
    block->FuncPtr(block);
    return 0;
}

block实际上就是一个结构体(__main_block_impl_0),其结构内部也有isa,所以block也是对象,也就能够被%@打印。
__main_block_impl_0这个结构体,其中一个参数就是__main_block_func_0,是一个执行函数,所以block在外层就是一个匿名函数,也叫代码块。
再看block的调用:block->FuncPtr(block); 这里之所以把block传进去,是因为这样可以在函数中获取block的数据。相当于oc的方法都有2个隐藏参数:self和cmd。

2、block捕获局部auto变量

通过xcrun编译下面代码:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        int a = 10;
        void(^block)(void) = ^{
            NSLog(@"%i",a);
        };
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

整理如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy 只读

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_c25834_mi_0,a);
        }

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, char * argv[]) {

    int a = 10;
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
    block->FuncPtr(block);
    return 0;
}

以上在block结构体中增加了变量a。在调用的时候,直接在函数内通过__cself获取a,然后进行值拷贝,从而打印。
如果此时对a进行修改:

 int a = 10;
void(^block)(void) = ^{
    a++;
    NSLog(@"%i",a);
};
block();

因为block内部是值拷贝,对a进行++操作之后,并不会对原本的a进行++。所以此时会造成编译器的代码歧义,编译是不通过的。

3、block捕获局部静态变量

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        static int a = 10;
        void(^block)(void) = ^(){
            NSLog(@"%i",a);
        };
        block();
        NSLog(@"%i",a);
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

通过xcrun编译如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_4824c8_mi_0,(*a));
}

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, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        static int a = 10;
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a));
        block->FuncPtr(block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_4824c8_mi_1,a);
    }
return UIApplicationMain(argc, argv, __null, appDelegateClassName);

可以看到block内部捕获的是a的地址指针。所以局部静态变量可以在block内进行修改。

4、block捕获全局变量

static int a = 10;
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        void(^block)(void) = ^(){
            NSLog(@"%i",a);
        };
        block();
        NSLog(@"%i",a);
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

编译如下:

static int a = 10;

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_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_ae4e59_mi_0,a);
}

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, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
        
        block->FuncPtr(block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_ae4e59_mi_1,a);
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

可以看到,block内部并没有捕获全局静态变量,而是直接使用,所以同样可以在block内部进行修改。把a写成全局变量(非静态)也是如此。

总结:
  • (1)当block捕获局部auto变量时,会进行值拷贝。
  • (2)当block捕获局部静态变量时,会进行指针拷贝。
  • (3)当block不会捕获全局变量,而是直接使用。

四、block修改局部变量

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        __block int a = 10;
        void(^block)(void) = ^{
            a++;
            NSLog(@"%i",a);
        };
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

我们都知道,想要在block内部修改局部变量,必须进行__block修饰,那么为什么经过__block修饰之后,就可以修改局部变量呢?
继续通过xcrun编译以上代码:

//byref的结构体
struct __Block_byref_a_0 {
  void *__isa;  //8
__Block_byref_a_0 *__forwarding; //8
 int __flags; //4
 int __size;//4
 int a;
};
//block的结构体
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;
  }
};

//copy函数 
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*/);}

//desc
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, char * argv[]) {
        //将int a 封装成结构体
        __Block_byref_a_0 a = {(void*)0,
            (__Block_byref_a_0 *)&a, //指向结构体本身
            0,
            sizeof(__Block_byref_a_0),
            10};
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0,
                                                  &__main_block_desc_0_DATA,
                                                  (__Block_byref_a_0 *)&a,
                                                  570425344));
        block->FuncPtr(block);
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

//函数体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝
    //a->__forwarding 是内部copy出来的那个block_byref
    (a->__forwarding->a)++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_12__nkjmvg1741fv4ynqnz517hr0000gn_T_main_c6ba7d_mi_0,(a->__forwarding->a));
}

以上是整理之后的完整的cpp代码。可以看到变量a,被封装成了__Block_byref_a_0结构体。而被block捕获的就是这个__Block_byref_a_0结构体指针。
在回调函数中,先获取block捕获到的__Block_byref_a_0指针,然后通过a(结构体指针)访问__forwarding,在通过__forwarding去修改a(值)。
由上可知,修改和读取的变量都是a->__forwarding->a,所以达到了在block内部修改局部变量的目的。

总结:

(1)block修改局部变量时,会把局部变量封装成相应的结构体。
(2)生成的结构体保存了原来变量的指针和值。
(3)block对其结构体指针进行捕获。

五、block的内存变化分析


首先在这个位置打一个断点,然后看汇编:

 0x1064d13d0 <+16>: leaq   0x3d79(%rip), %rdi        ; __block_literal_global

此时读寄存器register read rdi,然后打印:

(lldb) register read rdi
     rdi = 0x00000001064d5150  BlockTest`__block_literal_global
(lldb) po 0x00000001064d5150
<__NSGlobalBlock__: 0x1064d5150>
 signature: "v8@?0"
 invoke   : 0x1064d1400
 //....
(lldb) 

可以看到当block没有捕获外界局部非静态变量的时候,就是__NSGlobalBlock__。
接着让block捕获一个局部变量:

int a = 0;
void (^block)(void) =^{
    NSLog(@"%i",a);
};
block();

在这里打一个断点,然后register read rdi

<__NSStackBlock__: 0x7ff7b9b84828>
 signature: "v8@?0"
 invoke   : 0x1063793e0
 //...

此时block的类型就变成了__NSStackBlock__。
继续跳过objc_retainBlock断点之后,register read rax,此时block的类型变成了:

<__NSMallocBlock__: 0x600002564de0>
 signature: "v8@?0"
 invoke   : 0x1063793e0 
//...

通过分析,objc_retainBlock内调用了_Block_copy函数之后,block的类型才变成__NSMallocBlock__。
以上就是block在内存中的变化。

六、block的底层结构

首先block的结构如下:

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的底层结构就是Block_layout:
(1)它有一个isa指针,指向不同的block类型。
(2)flags是一个标识符

enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime 是否正在析构
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 引用计数的mask
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime 是否需要释放
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler    是否有copy、dispose函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler    //是否是全局block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler    //是否有签名
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

(3)reserved 保留字段
(4)invoke 函数指针,就是block的调用
(5)descriptor block的描述(重点)

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;//block的大小
};

// 可选
#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
};

可以看到,Block_descriptor_1是必然有的。
Block_descriptor_2是通过flags标记中的BLOCK_HAS_COPY_DISPOSE来判断是否存在。

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);
    //通过内存平移得到Block_descriptor_2
    return (struct Block_descriptor_2 *)desc;
}

Block_descriptor_3是通过flags标记中的BLOCK_HAS_SIGNATURE来判断是否存在。

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

block的结构分析完之后,尝试打印出来block的方法签名:
省略打断等操作,直接来到读取寄存器操作,拿到block的内存地址:

(lldb) register read rax
     rax = 0x0000600000071b00

po 0x0000600000071b00 打印出block

<__NSMallocBlock__: 0x600000071b00>
 signature: "v8@?0"
 invoke   : 0x10b1073e0//...

然后x/8gx读出block的内存情况:

(lldb) x/8gx 0x0000600000071b00
0x600000071b00: 0x00007ff866687ed8 0x00000000c1000002
0x600000071b10: 0x000000010b1073e0 0x000000010b10b130
0x600000071b20: 0x00007ff800000000 0x0000600000001c20
0x600000071b30: 0x0000000114b24688 0x0000000000000000

由block的内存结构可知,isa占8字节、flags和reserved共占8字节、invoke占8字节,所以descriptor1的内存地址为:0x000000010b10b130。
因为descriptor2和descriptor3都是通过descriptor1平移得到,所以打印0x000000010b10b130的内存情况:

(lldb) x/8gx 0x000000010b10b130
0x10b10b130: 0x0000000000000000 0x0000000000000024
0x10b10b140: 0x000000010b10a2f5 0x000000010b10a35d
0x10b10b150: 0x0000000000000000 0x0000000000000028
0x10b10b160: 0x000000010b107ca0 0x000000010b107ce0

因为descriptor1中reserved和size都是指针类型,所以各占8字节,那么0x000000010b10a2f5要么是BlockCopyFunction,要么是signature。

  • 通过 BLOCK_HAS_COPY_DISPOSE = (1 << 25) 判断是否有dispose函数。
    用 (1 << 25) & 0x00000000c1000002(block的flags,reserved为0),得到结果为0,所以它没有descriptor2。
  • 通过BLOCK_HAS_SIGNATURE = (1 << 30) 判断是否有签名。
    用 (1 << 30) & 0x00000000c1000002 得到 (unsigned int) $5 = 0x40000000,证明它有descriptor3。
    所以0x000000010b10a2f5一定是signature。
(lldb) p (char *)0x000000010b10a2f5
(char *) $0 = 0x000000010b10a2f5 "v8@?0"

此时完美验证。注意@?是blockobjc类型(block类型)

七、Block_copy分析

接着来分析,block是如何从栈区拷贝到堆区的。直接看源码:

// 栈 -> 堆
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;
    }
}

可以看到,传进来的参数arg被强转为Block_layout。然后判断是否需要释放,是否是全局block。
重点是当为栈block的时候:
(1)根据block的小大(aBlock->descriptor->size),在堆中申请内存,将栈block深拷贝到堆中。
(2)将invoke指向原来栈中的invoke,为了能调用原来的函数。
(3)设置flags标记。
(3)调用_Block_call_copy_helper函数,执行Block_descriptor_2中的copy函数。

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}

(4)将isa设置为_NSConcreteMallocBlock

八、block捕获外界变量时的三层拷贝

通过上面的Block_copy的分析可知,在_Block_call_copy_helper函数中,调用了Block_descriptor_2中的copy函数。
再通过上面分析的cpp代码可知:

//copy函数 
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    //这里的magicGeng是block_byref
    _Block_object_assign((void*)&dst->magicGeng, (void*)src->magicGeng, 8/*BLOCK_FIELD_IS_BYREF*/);}

copy函数会调用_Block_object_assign,接下来就分析Block_object_assign函数。

1、Block_object_assign

//destArg 是空指针 或者 是已经copy的
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];
        ********/
        // objc 指针地址 weakSelf (self)
        // 交给arc (这里其实是交给了系统级别的ARC进行相关的处理。)
        _Block_retain_object(object);
        // 持有地址指针
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
            
            // block 被一个 block 捕获

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        //__block修饰的变量,开始走到了这里
        /*******
         // 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:
        //__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;
    }
}

首先传进来的2个参数:destArg是待拷贝(或者是已经拷贝过block_byref)的指针,一个是通过编译器生成的原block_byref的指针。
先看一下Block_byref的结构:

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

isa不用管,因为在上面编译后的cpp代码中被复制为0。
forwarding,开始指向的是自身,这个下面具体分析。
flags,标记位:

enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler    //掩码
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler    
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler    
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler
    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime
    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler    //是否有COPY、DISPOSE函数
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime //是否需要释放
};

size 就是Block_byref的大小。

继续看_Block_object_assign函数,会枚举一下标记,也就是数据类型:

enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

分以下几种情况处理:

  • (1)当是BLOCK_FIELD_IS_OBJECT(对象类型)时,交给arc处理,然后进行地址指针的捕获。
    比如block捕获了一个对象,那么就会被block持有,对象的引用计数就会+1,当block释放之后,才会取消对这个对象的引用。
补充:当用__weak修饰时,编译cpp代码如下:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    
  NSObject *__weak weakself;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__weak _weakself, int flags=0) : weakself(_weakself) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{ _Block_object_assign((void*)&dst->weakself, (void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);}

可以看到,NSObject *__weak weakself;,然后交给_Block_object_assign函数,并且flag = BLOCK_FIELD_IS_OBJECT。此时因为是弱引用,不会对引用计数+1,所以__weak可以解决循环引用。(BLOCK_FIELD_IS_WEAK 这个标记注释写的很清楚,是仅在byref内使用)。

  • (2)当是BLOCK_FIELD_IS_BLOCK(也就是block嵌套)时,继续走_Block_copy函数。
  • (3)(重点)当时BLOCK_FIELD_IS_BYREF或者BLOCK_FIELD_IS_WEAK时,就走_Block_byref_copy
  • (4)当时BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT 以及BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_BLOCK 进行地址指针的捕获。
  • (5)当时BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK 以及 BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK 时,同样的进行地址指针的捕获。

2、重点分析_Block_byref_copy

//arg是传给block的原数据(src->Block_byref)
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // Block_byref  结构体
    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;
        
        // 问题 - block 内部 持有的 Block_byref 所持有的对象 是不是同一个
        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 {
            //如果是捕获的基础数据类型走这里(比如捕获的是int a)
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            //把值拷贝到copy中 (src->size - sizeof(*src) 就是要拷贝数据的长度)
            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;
}

核心流程如下:
(1)在堆中申请一片内存空间
(2)将copy的byref中的forwarding指向本身。
(3)将原来的byref中的forwarding指向copy。这样就可以达到在外层操作的forwarding是同一个对象。
(4)判断是否有copy和dispose,如果有则进行byref_keep和byref_destroy的赋值。如果没有证明是基础数据类型,直接进行内存拷贝。

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; // 结构体 __block  对象
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

(5)判断是否有BLOCK_BYREF_LAYOUT_EXTENDED(这个做为扩展)。
最后执行byref_keep函数。

那么byref_keep是执行的什么操作呢?
由上面分析的cpp代码可知:

//byref的结构体
struct __Block_byref_magicGeng_0 {
  void *__isa;
__Block_byref_magicGeng_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *magicGeng;
};

 __Block_byref_magicGeng_0 magicGeng = {(void*)0,(__Block_byref_magicGeng_0 *)&magicGeng,
            33554432,
            sizeof(__Block_byref_magicGeng_0),
            __Block_byref_id_object_copy_131,
            __Block_byref_id_object_dispose_131,
            objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new"))};

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

此时执行byref_keep,就是又一次执行了_Block_object_assign,此时传进去的参数是:
(1)已经拷贝的byref内存地址偏移40之后的对象
(2)原始的byref内存地址偏移40之后的对象。
另外重点是flags=131。
回顾上面的_Block_object_assign的枚举类型:
BLOCK_FIELD_IS_OBJECT | BLOCK_BYREF_CALLER = 131,也就是下面的判断:

 case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        //__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;

所以此时,会对外界被__block 修饰的那个对象进行一次指针持有,注意这里并没有增加其引用计数。所以这也就是为什么__block可以捕获并修改外界变量的原因。
当我们手动解决循环引用的时候,在block回调函数内进行置空,因为此时没有arc的参与,所以当取消指针引用的时候,对象的引用计数为0,即可释放。

总结:

(1)第一层copy是block自身的拷贝(从栈拷贝到堆)。(深拷贝)
(2)第二层copy是对编译器生成的block_byref的拷贝。(深拷贝)
(3)第三层是对block_byref内变量的拷贝。(浅拷贝)

九、问题补充

MRC下 __block 为什么可以解决循环引用?ARC下 __block为什么不能解决循环引用?
通过上面的底层分析,__blcok的原理已经非常的清晰了。关于这个问题,其实就是ARC和MRC机制的问题。

1、先来看MRC

demo如下:

- (void)testMRC {
    MRCObject *objc = [MRCObject new];
    [objc test];
}
//....

@interface MRCObject ()

@property (nonatomic, copy) void(^block)(void);

@end

@implementation MRCObject

- (void)test {
    __block typeof(self)blockSelf = self;
    self.block = ^() {
        NSLog(@"%@",blockSelf);
    };
    self.block();
}

- (void)dealloc {
    [super dealloc];
    NSLog(@"释放了");
}

@end

MRCObject 是一个MRC的类,当我们加入了__block之后,block内部会对当前的blockSelf进行指针持有,并没有对引用计数+1(上面分析的__Block_byref_id_object_copy_131)。所以__block在MRC下是可以解决循环引用的。
但是这里有个坑点,当我把代码改成:

- (void)test {
    __block typeof(self)blockSelf = self;
    self.block = ^() {
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"%@",blockSelf);
        });
    };
    self.block();
}

此时发生了崩溃,因为block内部对当前blockSelf进行的引用相当于是unsafe_unretained的。当延时函数执行时,当前的blockSelf已经被释放了,所以造成了野指针。
改为一下代码:

- (void)test {
    __block typeof(self)blockSelf = [self retain];
    self.block = ^() {
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"%@",blockSelf);
            [blockSelf release];
        });
    };
    self.block();
}

此时就解决了野指针问题,这种解决方案和上面分析的ARC下手动解决循环引用是一样的。

2、来看ARC

@interface ARCObject ()

@property (nonatomic, copy) void(^block)(void);

@end

@implementation ARCObject

- (void)test {
    __block typeof(self)blockSelf = self;
    self.block = ^() {
         NSLog(@"%@",blockSelf);
         blockSelf = nil;
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"释放了");
}

这种情况上面就分析过,必须在block内部手动调用blockSelf = nil,才能解决循环引用。
(1)blockSelf在进行赋值的时候,ARC机制会进行一次retain,没有问题。
(2)block内部会对blockSelf进行指针持有,不会对引用计数+1,这个也没有问题。
(3)问题是blockSelf出了作用域,blockSelf不会进行release吗?
假如进行了release,那么我们根本不用进行手动调用blockSelf = nil。调试结果证明,blockSelf出来作用域并没有release,猜想应该是ARC机制的判断。

总结:

(1)MRC下, __block可以解决循环引用,因为block内部只进行指针持有,但是可能会出现野指针问题。
(2)ARC下,__block不能彻底解决循环引用,因为ARC机制会对__block的对象进行一次retain操作,并且出了作用域不会release,所以需要手动在block回调中进行置空的操作才能解决循环引用。

十、流程总结