iOS--block原理探究

1,009 阅读28分钟

一、block的使用

1、作为变量

格式:

返回值类型 (^block的名称)(参数类型) = ^返回值类型(参数) {...};

例子:

//有参数和有返回值的block
   int (^blockName11)(NSString *name) = ^int(NSString *name){

       NSLog(@"%@",name);
       return 10;
   };
   blockName11(@"zhao");
//没参数和没返回值的block
   void (^blockName22)() = ^(){

       NSLog(@"123");
   };
   blockName22();

2、作为属性

格式:

@property (nonatomic, copy) 返回值类型 (^block的名称)(参数类型);

例子:

//有返回值有参数的属性
@property (nonatomic, copy) int (^blockName)(NSString *name);
//没返回值没参数的属性
@property (nonatomic, copy) void (^blockName1)();

3、作为方法声明的参数

格式:

-(void)方法名:(返回值类型 (^)(参数类型))block的名称;

例子:

-(void)testBlock:(int(^)(NSString *name))complateBlock;

4、作为方法实现的参数

格式:

[对象/类 方法名:^返回值类型 (参数) {...}];

例子:

    [self testBlock:^int(NSString *name) {
        
    }];

5、声明替换 -- typeDef

格式:

typedef 返回值类型 (^类型名称)(参数类型);

例子:

typedef int(^testBlock)(NSString *str);

6、block的循环引用

    __weak typeof(self) weakSelf = self; // weakSelf(弱引用表) -> self
    // strongSelf(nil) -> weakSelf -> self(引用计数不处理)--nil -> block -> weakSelf
    self.block = ^{
        // 持有 不能是一个永久持有 - 临时的
        // strong-weak-dance
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();
  • 如果在block中使用self,那么block会持有该self,假如同时self也持有block,就造成了循环引用
  • 我们可以在block外面创建一个弱引用weakSelf来替代self,这样block保存的weakSelf不会对self的引用计数造成影响,这样就不会造成循环引用了
  • block内部使用weakself可能是延迟使用,有可能当bock使用weakself时候self已经被释放了,这个时候weakself也变成nil了
  • 为了保证block内部使用weakself正常,这个时候就需要在block内部创建一个strongself指向weakself,这个时候strongself是block内部的临时变量,block会释放他,并且strongself和block的生命周期是一样的,所以可以保证block内部使用self正常

还有一种情况是self昨晚参数传到block里面:

    self.block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.block(self);

当self昨晚参数传进去时候,block自己会有个指针指向self,所以不会造成循环引用

二、block的分类

block有6种,这个我们可以在libclosure-73源码中可以看到

  • a、全局block

我们可以做一下测试:

    void (^block1)(void) = ^{
        NSLog(@"GlobalBlock--");
    };
    block1();
    NSLog(@"GlobalBlock:%@",block1);
    2020-03-08 20:34:13.013553+0800 BYTestBlock[33883:588331] GlobalBlock:<__NSGlobalBlock__: 0x10bdfd248>

打印出来的是全局block

  • b、堆block
    int a = 0;
    void (^block)(void) = ^{
        NSLog(@"GlobalBlock--%d",a);
    };
    NSLog(@"GlobalBlock:%@",block);
    
    2020-03-08 20:34:13.013699+0800 BYTestBlock[33883:588331] GlobalBlock:<__NSMallocBlock__: 0x600001bcb510>

打印出来的是堆block

  • c、栈block
    NSLog(@"GlobalBlock11:%@",^{ NSLog(@"GlobalBlock--%d",a);});
2020-03-08 20:34:13.013840+0800 BYTestBlock[33883:588331] GlobalBlock11:<__NSStackBlock__: 0x7ffee3e13478>

打印出来的是栈block

上面三种是我们常用的三种,还有另外三种一般是系统使用的,暂时不做讨论

三、clang分析

我们首先看一下编译器会把block的OC代码转成什么样的c++代码

  • 1、在OC源文件block.m写好代码。
  • 2、打开终端,cd到block.m所在文件夹。
  • 3、输入clang -rewrite-objc block.m,就会在当前文件夹内自动生成对应的block.cpp文件。

oc代码:

int main(int argc, char * argv[]) {
    @autoreleasepool {

        void (^blockName22)() = ^(){
            printf("Block\n");
         };
        blockName22();
    }
}

c++代码

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("Block\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(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*blockName22)() = ((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);


    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

a、block为什么能用%@打印

首先我们看看blockName22这句代码的转换:

        void (*blockName22)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

去掉多余的强转

void (*blockName22)() = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

我们发现blockName22本身是__main_block_impl_0,有点像构造函数,我们再看看__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;
  }
};

我们发现__main_block_impl_0其实是一个结构体,所以block本质上是一个OC对象,所以可以用 %@ 来打印

b、block是如何调用的

我们再看一下__main_block_impl_0里面的构造函数__main_block_impl_0,有三个参数:

  • 1、fp:我们发现传的是__main_block_func_0,而她__main_block_func_0其实就是一个函数,传进来后会把他保存在impl.FuncPtr上面,这种写法叫做函数式编程,然后我们看到下面一句代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

去掉强转符号:

(__block_impl *)block->FuncPtr

所以block()其实就是调用一个函数的过程,这就是block的调用过程

c、block捕获变量

oc代码:

        NSInteger a = 9;
        __block NSInteger b = 9;
        NSMutableArray *array = [NSMutableArray array];
        [array addObject:[NSObject new]];
        void (^block)(void) = ^{
//            index++;//报错
            b++;
            [array addObject:@"1"];
            NSLog(@"%ld  %ld   %ld",a,b,array.count);
        };
        block();
        a++;
        block();
        NSLog(@"%ld  %ld   %ld",a,b,array.count);
        
2020-03-06 14:58:07.102646+0800 BYTestBlock[96733:7875542] 9  10   2
2020-03-06 14:58:07.103382+0800 BYTestBlock[96733:7875542] 9  11   3
2020-03-06 14:58:07.103481+0800 BYTestBlock[96733:7875542] 10  11   3

然后我们把这段代码转换成C++代码

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger b;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  NSInteger a;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__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_b_0 *b = __cself->b; // bound by ref
  NSMutableArray *array = __cself->array; // bound by copy
  NSInteger a = __cself->a; // bound by copy


            (b->__forwarding->b)++;
            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->array, 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, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSInteger a = 9;
        __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};
        NSMutableArray *array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        a++;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_2,a,(b.__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们在c++代码中发现__block NSInteger b = 9;代码被转换成了__Block_byref_b_0结构体

__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};

而__Block_byref_b_0结构是:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger b;
};

然后我们看一下通过构造函数创建的__Block_byref_b_0传参

  • 1、__isa:传的是(void*)0,其实就是空
  • 2、__forwarding:传的是外面b变量的指针,也就是self自己
  • 3、__flags:传的是0
  • 4、__size:传的是__Block_byref_b_0的大小
  • 5、b:传的是变量b的值

我们再看一下__main_block_impl_0的结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  NSInteger a;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我们发现传进来的参数都直接赋给impl的属性了

再看一下构造函数的调用:

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));

去掉类型强转:

void (*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));

总共变成了6个参数

  • 1、__main_block_func_0
  • 2、&__main_block_desc_0_DATA
  • 3、array
  • 4、a
  • 5、(__Block_byref_b_0 *)&b
  • 6、570425344

再看一下__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  NSMutableArray *array = __cself->array; // bound by copy
  NSInteger a = __cself->a; // bound by copy


            (b->__forwarding->b)++;
            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
  • 我们发现调用__main_block_func_0其实就是block对象__main_block_impl_0,在实现函数里面会将block对象的参数属性都进行指针copy一份,
  • 如果外面变量没加__block,那copy过来的参数跟外面是一样的,,假如直接修改该参数就会造成冲突,不知道是修改block内部的变量还是外面的变量,所以会报错,
  • 而加了__block系统其实会自动在创建一个结构体,将新创建的指针传进来,这样就不会和外面的发生冲突
  • 当修改外面变量时候调用的是 (b->__forwarding->b)++;而新创建的结构体里面有外卖变量的指针,在block内部所以修改变量时候其实修改的就是外面的变量

四、block的内存变化

原理

首先我们先了解一下数据结构,我们在转的.cpp文件中搜索一下Block_private

我们发现很多关于block的函数都提示在block_private.h里面,我们打开源码libclosure-73,然后在block_private.h,我们先找到基础block空间Block_layout,这个结构跟我们在cpp里面看的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
};
  • 1、isa:指向父类的指针
  • 2、flags:记录状态的
  • 3、reserved:
  • 4、invke:函数
  • 5、descriptor:

关于flags我们可以看到上面有个描述:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime =(0x0001)释放标记,一般常用
    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
};

关于Flags标示:

  • 第1位:释放标记,一般常用BLOCK_NEEDS_FREE做位与操作,一同传入Flags,告知该block可释放
  • 第16位:存储引用计数的值,是一个可选用参数
  • 第24位:第16位是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的值
  • 第25位:是否拥有拷贝辅助函数
  • 第26位:是否拥有block析构函数
  • 第27位:标志是否有垃圾回收
  • 第28位:标志是否是全局block
  • 第30位:与BLOCK_USE_STRET相对,判断是否当前block拥有一个签名。用于runtime时动态调用

我们在Block_layout里面看到一个Block_descriptor_1,并且在上面还看到了:

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

我们发现还有Block_descriptor_2和Block_descriptor_3,这是因为这两个是可选的,我们来到runtime.cpp文件搜索一下Block_descriptor_2找到下面:

#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
    return aBlock->descriptor;
}
#endif

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;
}
  • 我们发现在获取Block_descriptor_2时候,是要先判断(aBlock->flags & BLOCK_HAS_COPY_DISPOSE),也就是要拿到block的flags和BLOCK_HAS_COPY_DISPOSE与一下,看看是否拥有copy辅助函数存在,如果不存在就返回空

  • 同样我们看到Block_descriptor_3也是需要判断一下(aBlock->flags & BLOCK_HAS_SIGNATURE),来判断这个block是否有签名,因为Block_descriptor_3里面有个属性是signature

  • 那怎么获取Block_descriptor_2和Block_descriptor_3呢,我们看到获取_Block_descriptor_2时候是在_Block_descriptor_1的指针上加上_Block_descriptor_1的大小,

  • 而_Block_descriptor_3是在_Block_descriptor_1的指针上加上_Block_descriptor_1和_Block_descriptor_2的大小,这说明三者是连续的,这是内存偏移原理

测试已经block的copy过程:

下面我们用实例来测试下,我们先写一段代码,在block哪里加上断点

打开汇编调试运行:

我们发现调用了objc_retainBlock函数,我们在这个地方加一个断点然后跳到该函数里面去

,因为调用该函数是block调用的,调用者是x0,所以x0就是我们定义的block,我们打印下x0

我们发现此时block是__NSGlobalBlock__ ,我们再点击in键一步一步往下走

再往下点击

我们进入到了_Block_copy函数里面,因为调用该函数是block调用的,调用者是x0,所以x0就是我们定义IDEblock,我们打印下x0

发现该block还是__NSGlobalBlock__,说明block性质没有发生变化

我们把上面代码做一下更改:

然后按照上面顺序执行:

这个地方有点不一样了,,我们不管objc_retainBlock之前的汇编代码,进入到objc_retainBlock函数,然后打印x0

我们发现此时block是__NSStackBlock__栈block, 继续往下走

我们发现调动的函数_Block_copy,我们可以在符号断点地方给_Block_copy加个断点

我们进入到_Block_copy函数里面

其实我们研究是栈block通过copy如何变成堆block的,所以我们直接看一下return到底返回的是啥就知道了

我们发现刚进入到_Block_copy时候的block是栈block,然后我们直接在最后的return地方打个断点,然后看下返回的是啥就知道最终返回的是啥block了

我们最后发现在return地方x0的内存地址发生了变化,并且变成了一个堆block

那么为什么这个时候栈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;
    }
}

并在在里面会对block的flags做这个判断,

如果block是全局的,那么就直接返回block本身,如果不是,那么就往下走,往下走就是通过malloc在堆区开辟内存空间,然后将原block的信息都copy到堆创建的block中,其中我们注意一下

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

当flags&BLOCK_NEEDS_FREE有值的时候给block的引用计数器加2,为啥加2呢,因为==1时候是作为释放标示的,这个在下面,将block从栈区copy到堆区时候也可以看到

block的调用

继续上面代码,加上断点,然后打开汇编

运行调试:

我们发现在objc_retainBlock后面还有个objc_release,objc_retainBlock算是block创建完成,从堆区copy到栈区,,而objc_release是将block释放了,所以block的调用肯定在这两行之间,所以我们在第29行出加个断点,然后跳到这,我们先看一下block对象,也就是x0

我们发现此时已经是一个堆block了,我们在看一下29行

->  0x104852120 <+108>: ldr    x8, [x0, #0x10]

意思就是调用函数的意思,[x0, #0x10]意思就是从x0指针右移16个字节,为啥是右移16字节呢,我们看下Block_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的指针就是isa的位置,而invoke就是需要调用的block的函数指针,而invoke距离isa的位置就是16个自己,所以block位移16个自己就是block的函数指针的位置

这个时候位移16个自己得到x8,我们往下跳一步后,打印一下x8

我们发现确实是_block_invoke,我们进到这个步里面去看一下

确实是block的函数实现

block的签名

依然像上面代码一样,加一个断点,然后在block retain后打印出block的地址,再通过x/5gx打印出该对象的内存情况

我们先看下block的结构Block_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
};

struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

而且我们知道block的签名在Block_descriptor_3里面,并且需要aBlock->flags & BLOCK_HAS_SIGNATURE都成立才有Block_descriptor_3,我们来测试下是否成立

  • 我们flag在isa后面,所以此时flags是0x00000000c1000002的后8位c1000002 我们用mac自带的计算器,先找到BLOCK_HAS_SIGNATURE的值
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler

也就是10进制下1左移30位得到16进制的0x40000000,然后&上flags值c1000002得到0x40000000,不为空,所以Block_descriptor_3是有值的,

  • 我们还可以判断一下aBlock->flags & BLOCK_HAS_COPY_DISPOSE,我们先拿到BLOCK_HAS_COPY_DISPOSE值
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler

也就是10进制下1左移30位得到16进制的0x40000000,然后&上flags值c1000002得到0x0,为空,也就是没有Block_descriptor_2

我们再来拿到Block_descriptor_3中的signature值

  • 我们先得到block的内存地址,然后打印出block的内存情况,因为descriptor是block的第五个属性,但是第二个和第三个都是4字节,共用一个8字节的内存地址,所以descriptor的内存地址是block的第四块0x0000000102f70018,
  • 而descriptor地址就是就是Block_descriptor_1的地址,我们尝试打印下Block_descriptor_1的信息,发现打印不出来,所以我们只能尝试找到signature的内存地址,然后打印出signature的值,
  • 从上面我们知道Block_descriptor_1、Block_descriptor_2、Block_descriptor_3,地址是连续的,但是这次Block_descriptor_2不存在,所以Block_descriptor_3内存是在Block_descriptor_1后面
  • 我们通过x/4gx打印出Block_descriptor_1后面的所有内存块情况,因为Block_descriptor_1大小是16字节,所有Block_descriptor_3就是内存地址是0x0000000102f70028,而这块内存里面前半部分的值是0x0000000102f6f366
  • 所以确定signature值就是0x0000000102f6f366,然后po出来得到了"v8@?0"

然后我们再打印出descriptor的内存情况,上面我们测试知道Block_descriptor_2是不存在的,所以Block_descriptor_3就是第三块内存0x0000000100703366,然后我们打印出 Block_descriptor_3的值就得到block的签名

__block修饰,已经block的三层拷贝

授信我们在main文件写一个带__block参数的block

然后通过命令xcrun -sdk iphonesimulator clang -rewrite-objc main.m转成.cpp文件

但是我们发现转换失败,提示clang不支持,这是因为__block修饰的变量直接赋值常量字符串导致的,我们把它改一下,然后再转换

然后拿到了main.cpp文件,这是因为clang没法和当前Xcode没法完美适配,所以这种情况下, 字面量尽量少用

然后我们拉到最下面,看到生成的c++代码


struct __Block_byref_lg_name_0 {
  void *__isa;
__Block_byref_lg_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *lg_name;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_lg_name_0 *lg_name; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_lg_name_0 *_lg_name, int flags=0) : lg_name(_lg_name->__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_lg_name_0 *lg_name = __cself->lg_name; // bound by ref

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->lg_name, 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, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));

        __attribute__((__blocks__(byref))) __Block_byref_lg_name_0 lg_name = {(void*)0,(__Block_byref_lg_name_0 *)&lg_name, 33554432, sizeof(__Block_byref_lg_name_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_bebf93_mi_0)};
        void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_lg_name_0 *)&lg_name, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们来一点点分析,先看一下:

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

我们发现结构体__main_block_desc_0_DATA是__main_block_desc_0类型的

  • 第一个参数是0
  • 第二个参数是__main_block_impl_0的大小,而__main_block_impl_0就是我们定义的block转换成的c++函数
  • 第三个参数是__main_block_copy_0
  • 第四个参数是__main_block_dispose_0

而__main_block_copy_0和__main_block_dispose_0这两个函数又跟我们上面说的Block_byref_2一一对应

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

而__main_block_copy_0函数里面还调用了_Block_object_assign函数

我们在分析一下_Block_object_assign函数,看一下源码

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// hold objects - 自动捕获到变量
// lgname
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;
    }
}

我们发现是对不同枚举有不同的操作,我们对应一下枚举的含义

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变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable,__block修饰的结构体
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers ,__weak修饰的变量
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.,处理block_bref内部对象内存的时候会加一个额外的标记,配合上面的枚举一起使用
};

如果是BLOCK_FIELD_IS_OBJECT的话,会调用_Block_retain_object,我们进去看一下

static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

static void _Block_retain_object_default(const void *ptr __unused) { }

我们发现什么都没做,只是因为对象的生命周期是交给ARC做了,这边只需要将指针赋值过去即可

但是这次传进来的是用__block修饰的变量,所以我们看一下是BLOCK_FIELD_IS_BYREF的话是怎么处理的,我们发现是调用了_Block_byref_copy函数然后返回一个对象过来的,我们进入到_Block_byref_copy函数实现看看:

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
        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具有修改能力
        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;
}

我们发现在这个函数里面通过malloc在堆区创建了传进来参数相同的内存空间,并且将原来block的forwarding指向了新block,因为原block的forwarding指向的是自己,这样就实现了在block内部可以修改原来变量的能力,这是为什么呢,我们来看看,

首先我们在.cpp文件中,我们发现原来:

  • 我们定义的block被转换成了__main_block_impl_0
  • 而用__block修饰的变量lg_name也被转换成了__Block_byref_lg_name_0结构体
  • 而在__main_block_impl_0结构体中有个属性lg_name是__Block_byref_lg_name_0类型的,并且在使用构造函数__main_block_impl_0的时候将栈区的__Block_byref_lg_name_0
  • 在_Block_object_assign函数调用时是将__main_block_impl_0的lg_name传进去的,在_Block_object_assign会将原来的栈区的__Block_byref_lg_name_0结构体malloc到堆区,并且将栈区的__Block_byref_lg_name_0的__forwarding指向了堆区的__Block_byref_l,而堆区的__Block_byref_lg_name_0才是block内部的变量
  • 因为此时栈区的__Block_byref_lg_name_0的__forwarding指针指向的是堆区的__Block_byref_lg_name_0,所以当栈区的__Block_byref_lg_name_0修改lg_name其实修改的是堆区的__Block_byref_lg_name_0的lg_name

我们再往下看:

Block_byref_2 *src2 和Block_byref_2 *copy2都是在Block_byref的指针的基础上 加1得到的,所以Block_byref和Block_byref_2的内存是连续的,我们再在.cpp文件中看一下__Block_byref_lg_name_0结构

并且Block_byref的结构是这样的

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

所以src2里面两个属性的值也就是__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131

当src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE成立时候,src2调用了byref_keep函数,而byref_keep也就是__Block_byref_id_object_copy_131,我们回到.cpp里面再看看__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);
}

发现调用了_Block_object_assign,我们看到参数为啥加40呢?

  • 第一调用时候(*src2->byref_keep)(copy, src);传进来的参数是copy和src,也就是堆区的__Block_byref_lg_name_0和栈区的__Block_byref_lg_name_0,
  • 而__Block_byref_lg_name_0的结构是
struct __Block_byref_lg_name_0 {
  void *__isa;
__Block_byref_lg_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *lg_name;
};

加上40个字节正好是__Block_byref_lg_name_0的lg_name的内存地址。而_Block_object_assign上面已经分析过了

总结:

  • 当有__block修饰的变量,并且把该变量用于自定义block时候,系统会自动将变量类型改为__Block_byref_变量名_0类型的结构体,自定义block改为__main_block_impl_0类型的结构体
  • 并且__main_block_impl_0结构在创建时候是将__main_block_func_0、__main_block_desc_0_DATA、__Block_byref_lg_name_0保存起来
  • 然后系统调用了__main_block_desc_0_DATA,再调用了_Block_object_assign函数,传进去的参数是栈区block的__Block_byref_lg_name_0和堆区block的__Block_byref_lg_name_0的指针
  • 在_Block_object_assign函数里面判断传进来的参数如果用了__block修饰了,那么就使用_Block_byref_copy把栈区block的__Block_byref_lg_name_0属性copy到堆区,并且同时将栈区对象的forwarding指向到堆区的对象,
  • copy过程中。判断该对象的flags如果是BLOCK_BYREF_HAS_COPY_DISPOSE,则调用该参数的byref_keep属性方法,也就是__Block_byref_id_object_copy_131
  • 然后在__Block_byref_id_object_copy_131方法里面载调用_Block_object_assign方法,但是此时传进去的值是生成的变量结构体的NSString *lg_name值,这一步就是将栈区就结构体的变量值传堆区结构体的变量属性
  • 栈区的__Block_byref_lg_name_0属性copy到堆区完成后,再将堆区的__Block_byref_lg_name_0指针赋给堆区block的__Block_byref_lg_name_0的指针

当系统将栈区block拷贝到堆区后,也会将栈区用__block修饰的参数拷贝到堆区,因为__block修饰的参数系统会自动把他变为结构体形式,其中参数名只是结构体的一个属性,栈区参数拷贝到堆区后,又会将结构体的参数对象也copy一下,这就是block牛逼的三层copy

另一种分析

首先我们看一下从原来的block值(OC代码块)转化而来的C++代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("Block\n");
}

这里,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,OC里的self),而且很容易看出来,__cself 是指向__main_block_impl_0结构体实现的指针。 结合上句话,也就是说Block结构体就是__main_block_impl_0结构体。Block的值就是通过__main_block_impl_0构造出来的。

下面来看一下__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;
  }
};

可以看出,__main_block_impl_0结构体有三个部分:

  • 1、第一个是成员变量impl,它是实际的函数指针,它指向__main_block_func_0。来看一下它的结构体的声明:
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;  //今后版本升级所需的区域
  void *FuncPtr; //函数指针
};

我们发现这个结构体跟OC对象的结构体是一样的,所以block本质上就是一个对象,并且isa指向的是&_NSConcreteStackBlock,我们可以在libclosure-73源码中找到

这里我们发现block其实是有6中的,其中_NSConcreteGlobalBlock、_NSConcreteMallocBlock、_NSConcreteStackBlock是我们经常用到的,其他三种只是在系统中才用到

  • 2、第二个是成员变量是指向__main_block_desc_0结构体的Desc指针,是用于描述当前这个block的附加信息的,包括结构体的大小等等信息
static struct __main_block_desc_0 {
    
  size_t reserved;  //今后升级版本所需区域
  size_t Block_size;//block的大小
    
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 3、第三个部分是__main_block_impl_0结构体的构造函数,__main_block_impl_0 就是该 block 的实现
__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;
  }

在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。__main_block_imlp_0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。

2、Block截获自动变量和对象

a、变量的使用

        NSInteger a = 9;
        __block NSInteger b = 9;
        NSMutableArray *array = [NSMutableArray array];
        [array addObject:[NSObject new]];
        void (^block)(void) = ^{
//            index++;//报错
            b++;
            [array addObject:@"1"];
            NSLog(@"%ld  %ld   %ld",a,b,array.count);
        };
        block();
        a++;
        block();
        NSLog(@"%ld  %ld   %ld",a,b,array.count);

2020-03-06 14:58:07.102646+0800 BYTestBlock[96733:7875542] 9  10   2
2020-03-06 14:58:07.103382+0800 BYTestBlock[96733:7875542] 9  11   3
2020-03-06 14:58:07.103481+0800 BYTestBlock[96733:7875542] 10  11   3
  • 1、block可以使用外部的参数,但是如果要在block内部修改外部的参数,需要在参数前添加block
  • 2、即使在Block外部修改这些变量,存在于Block内部的这些变量也不会被修改
  • 3、这里的修改是指整个变量的赋值操作,变更该对象的操作是允许的,比如在不加上__block修饰符的情况下,给在block内部的可变数组添加对象的操作是可以的

b、原理

我们将.m文件转换成.cpp文件


struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger b;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  NSInteger a;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__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_b_0 *b = __cself->b; // bound by ref
  NSMutableArray *array = __cself->array; // bound by copy
  NSInteger a = __cself->a; // bound by copy


            (b->__forwarding->b)++;
            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->array, 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, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSInteger a = 9;
        __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};
        NSMutableArray *array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        a++;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_2,a,(b.__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));


    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们先看看

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  NSInteger a;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 1、我们可以看到,在block内部语法表达式中使用的自动变量(a、array、b)被作为成员变量追加到了__main_block_impl_0结构体中(注意:block没有使用的自动变量不会被追加)。
  • 2、在初始化block结构体实例时(请看__main_block_impl_0的构造函数),还需要截获的自动变量a、b、array来初始化__main_block_impl_0结构体实例,因为增加了被截获的自动变量,block的体积会变大。
  • 3、b变量声明时候加了__block,在block内部拿到的是b变量的指针,所以在block内部能修改b变量

但是我们发现b变量变成了__Block_byref_b_0,我们看看这个结构体类型:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger b;
};

我们可以看到,这个结构体最后的成员变量就相当于原来自动变量。 这里有两个成员变量需要特别注意:

  • 1、b:保存了最初的b变量,也就是说原来单纯的int类型的b变量被__block修饰后生成了一个结构体。这个结构体其中一个成员变量持有原来的b变量。
  • 2、__forwarding:通过__forwarding,可以实现无论__block变量配置在栈上还是堆上都能正确地访问__block变量,也就是说__forwarding是指向自身的。

再来看一下函数体的代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  NSMutableArray *array = __cself->array; // bound by copy
  NSInteger a = __cself->a; // bound by copy


            (b->__forwarding->b)++;
            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }