前言

Block源码分析已经有太多博客了,这篇文章的特点是通过clang –rewrite-objc file.m指令,把objc文件转换成c++文件,通过捕获不同的类型的变量来分析block的行为

1. Block

1.1 block也是一个对象

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;
  }
};
  • 主要分为Imp结构体和Desc结构体

    1.2 Block是类吗?有哪些类型?

    一般来说block有三种:_NSConcreteGlobalBlock、_NSConcreteStackBlock、_NSConcreteMallocBlock,根据Block对象创建时处数据区不同而进行区分

  • 栈上block:引用了栈上变量,生命周期由系统控制的,一旦所属作用域结束,就被系统销毁
  • 堆上block:使用copy或者strong,从栈上copy到堆上
  • 全局block:未引用任何栈上变量是就是全局block,会被保存到__data区域,也就是数据段
    NSInteger val = 1;
    __unsafe_unretained dispatch_block_t block = ^{};
    __unsafe_unretained dispatch_block_t unsafeUnretainedBlockRefLocalVar = ^{
        NSLog(@"%ld", val);
    };
    __strong dispatch_block_t strongBlock = ^{};
    __strong dispatch_block_t strongBlockRefLocalVar = ^{
        NSLog(@"%@", object);
    };
    __autoreleasing dispatch_block_t autoReleasingBlock = ^{
        NSLog(@"%@", object);
    };
    NSLog(@"%@", block);
    NSLog(@"%@", unsafeUnretainedBlockRefLocalVar);
    NSLog(@"%@", strongBlock);
    NSLog(@"%@", strongBlockRefLocalVar);
    NSLog(@"%@", autoReleasingBlock);

ARC有效的情况下,通过这样的方式打印block,也可以通过object_getClass打印,结果是一样的

<__NSGlobalBlock__: 0x1031999a8>
<__NSMallocBlock__: 0x1362d69d0>
<__NSGlobalBlock__: 0x1031999e8>
<__NSMallocBlock__: 0x1362d6f10>
<__NSMallocBlock__: 0x1362d4de0>
  • 可以看到,在ARC有效的情况下,即使block被修饰为__unsafe_unretained,还是会被copy到堆上
  • 不捕获局部变量的block是GlobalBlock

1.3 捕获变量

捕获全局变量和自动变量有什么区别? 捕获ivar和属性有什么区别?

@interface LabObject ()

@property (nonatomic, assign) CGFloat propertyVal;

@end

@implementation LabObject {
    CGFloat *_labIvar;
}

- (void)testBlock {
    static CGFloat val = 1;
    CGFloat localVal = 1;
    dispatch_block_t block = ^{
        val = 2;
        NSLog(@"%f", localVal);
        self.propertyVal = 3;
        self->_labIvar = 4;
    };

    block();

    NSLog(@"%@", block);
    NSLog(@"%f", val);
}

@end

转换成c++后的代码

struct __LabObject__testBlock_block_impl_0 {
  struct __block_impl impl;
  struct __LabObject__testBlock_block_desc_0* Desc;
  CGFloat *val;
  CGFloat localVal;
  LabObject *self;
  __LabObject__testBlock_block_impl_0(void *fp, struct __LabObject__testBlock_block_desc_0 *desc, CGFloat *_val, CGFloat _localVal, LabObject *_self, int flags=0) : val(_val), localVal(_localVal), self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __LabObject__testBlock_block_func_0(struct __LabObject__testBlock_block_impl_0 *__cself) {
  CGFloat *val = __cself->val; // bound by copy
  CGFloat localVal = __cself->localVal; // bound by copy
  LabObject *self = __cself->self; // bound by copy

        (*val) = 2;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_250d18_mi_0, localVal);
        ((void (*)(id, SEL, CGFloat))(void *)objc_msgSend)((id)self, sel_registerName("setPropertyVal:"), (CGFloat)3);
        (*(CGFloat *)((char *)self + OBJC_IVAR_$_LabObject$_labIvar)) = 4;
    }
static void __LabObject__testBlock_block_copy_0(struct __LabObject__testBlock_block_impl_0*dst, struct __LabObject__testBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __LabObject__testBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __LabObject__testBlock_block_impl_0*, struct __LabObject__testBlock_block_impl_0*);
  void (*dispose)(struct __LabObject__testBlock_block_impl_0*);
} __LabObject__testBlock_block_desc_0_DATA = { 0, sizeof(struct __LabObject__testBlock_block_impl_0), __LabObject__testBlock_block_copy_0, __LabObject__testBlock_block_dispose_0};

static void _I_LabObject_testBlock(LabObject * self, SEL _cmd) {
    static CGFloat val = 1;
    CGFloat localVal = 1;
    dispatch_block_t block = ((void (*)())&__LabObject__testBlock_block_impl_0((void *)__LabObject__testBlock_block_func_0, &__LabObject__testBlock_block_desc_0_DATA, &val, localVal, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_250d18_mi_1, block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_250d18_mi_2, val);
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSString *string = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_250d18_mi_3);
    }
}

static CGFloat _I_LabObject_propertyVal(LabObject * self, SEL _cmd) { return (*(CGFloat *)((char *)self + OBJC_IVAR_$_LabObject$_propertyVal)); }
static void _I_LabObject_setPropertyVal_(LabObject * self, SEL _cmd, CGFloat propertyVal) { (*(CGFloat *)((char *)self + OBJC_IVAR_$_LabObject$_propertyVal)) = propertyVal; }
  • 命名:block结构体和函数的命名逻辑是,所在class的名称+所在方法的名称+block的结构体的名称
  • flag:只捕获自动变量和全局变量的时候,block的初始化函数不会给flag赋值,但是加上self的时候,有了赋值
    • 570425344 用十六进制是 0x22000000:有copy/dispose方法(捕获了OC对象或__block变量)
    • flag主要用于记录block的特性,运行时和内存管理会根据flag做不同的操作
  • 捕获变量:
    • 全局变量:可以看到,捕获的全局变量和捕获实例一样,是一个指针
    • 自动变量:自动变量捕获值,不能修改
    • 属性和ivar:捕获self
  • testBlock方法:对block的初始化非常简单
    • 传入调用函数的指针
    • 需要的参数,变量和flag等
  • 赋值:属性通过msg_send赋值,ivar通过类内偏移找到内存地址然后赋值
  • 引用循环:所以在使用block的时候要小心相互强持有带来的引用循环

1.4 block在修改NSMutableArray的时候,需不需要添加__block?

不需要,block内部捕获了“指针”,除非要修改对象本身,不然不需要__block

1.5 捕获自动变量的实例

因为这个实验是写了下面的才想起来,所以忽略这里的__block修饰符

- (void)testBlock {
    __block CGFloat localVal = 1;
    LabObject *object = [[LabObject alloc] init];
    dispatch_block_t block = ^{
        localVal = 2;
        NSLog(@"%@", object);
    };

    block();

    NSLog(@"%@", block);
}

struct __LabObject__testBlock_block_impl_0 {
  struct __block_impl impl;
  struct __LabObject__testBlock_block_desc_0* Desc;
  LabObject *object;
  __Block_byref_localVal_0 *localVal; // by ref
  __LabObject__testBlock_block_impl_0(void *fp, struct __LabObject__testBlock_block_desc_0 *desc, LabObject *_object, __Block_byref_localVal_0 *_localVal, int flags=0) : object(_object), localVal(_localVal->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

dispatch_block_t block = ((void (*)())&__LabObject__testBlock_block_impl_0((void *)__LabObject__testBlock_block_func_0, &__LabObject__testBlock_block_desc_0_DATA, object, (__Block_byref_localVal_0 *)&localVal, 570425344));
  • 自动变量实例:捕获指针,在新建block的时候,传入的也是object
  • 为什么会报错:GPT回答这是一个编译器行为,编译器保证未使用__block修饰符的实例不能被改变

1.6 __block修饰符

因为上面的实验,略微修改这个实验,加上上面的object,并加上__block修饰符

- (void)testBlock {
    __block CGFloat localVal = 1;
    __block LabObject *object = [[LabObject alloc] init];
    dispatch_block_t block = ^{
        localVal = 2;
        object = nil;
    };

    block();

    NSLog(@"%@", block);
}
Clang -rewrite-objc
struct __Block_byref_localVal_0 {
  void *__isa;
__Block_byref_localVal_0 *__forwarding;
 int __flags;
 int __size;
 CGFloat localVal;
};
struct __Block_byref_object_1 {
  void *__isa;
__Block_byref_object_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 LabObject *object;
};

struct __LabObject__testBlock_block_impl_0 {
  struct __block_impl impl;
  struct __LabObject__testBlock_block_desc_0* Desc;
  __Block_byref_localVal_0 *localVal; // by ref
  __Block_byref_object_1 *object; // by ref
  __LabObject__testBlock_block_impl_0(void *fp, struct __LabObject__testBlock_block_desc_0 *desc, __Block_byref_localVal_0 *_localVal, __Block_byref_object_1 *_object, int flags=0) : localVal(_localVal->__forwarding), object(_object->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __LabObject__testBlock_block_func_0(struct __LabObject__testBlock_block_impl_0 *__cself) {
  __Block_byref_localVal_0 *localVal = __cself->localVal; // bound by ref
  __Block_byref_object_1 *object = __cself->object; // bound by ref

        (localVal->__forwarding->localVal) = 2;
        (object->__forwarding->object) = __null;
    }
static void __LabObject__testBlock_block_copy_0(struct __LabObject__testBlock_block_impl_0*dst, struct __LabObject__testBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->localVal, (void*)src->localVal, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->object, (void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __LabObject__testBlock_block_dispose_0(struct __LabObject__testBlock_block_impl_0*src) {_Block_object_dispose((void*)src->localVal, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __LabObject__testBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __LabObject__testBlock_block_impl_0*, struct __LabObject__testBlock_block_impl_0*);
  void (*dispose)(struct __LabObject__testBlock_block_impl_0*);
} __LabObject__testBlock_block_desc_0_DATA = { 0, sizeof(struct __LabObject__testBlock_block_impl_0), __LabObject__testBlock_block_copy_0, __LabObject__testBlock_block_dispose_0};

static void _I_LabObject_testBlock(LabObject * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_localVal_0 localVal = {(void*)0,(__Block_byref_localVal_0 *)&localVal, 0, sizeof(__Block_byref_localVal_0), 1};
    __attribute__((__blocks__(byref))) __Block_byref_object_1 object = {(void*)0,(__Block_byref_object_1 *)&object, 33554432, sizeof(__Block_byref_object_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((LabObject *(*)(id, SEL))(void *)objc_msgSend)((id)((LabObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LabObject"), sel_registerName("alloc")), sel_registerName("init"))};
    dispatch_block_t block = ((void (*)())&__LabObject__testBlock_block_impl_0((void *)__LabObject__testBlock_block_func_0, &__LabObject__testBlock_block_desc_0_DATA, (__Block_byref_localVal_0 *)&localVal, (__Block_byref_object_1 *)&object, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_d1f283_mi_0, block);
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSString *string = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_s2_b47p20rj57193kb9y3d640wm0000gn_T_LabObject_d1f283_mi_1);
    }
}
  • __Block_byref_localVal_0:用来描述__block修饰的结构体
    • isa:兼容对象指针模型
    • __flags:标志位,堆栈,已释放
    • __size:结构体大小
    • localVal:变量
  • 当block的作用域结束,就会被废弃,__block修饰的实例也是一样
  • __forwarding:当block被从栈copy到堆上的时候,__block修饰符修饰的对象也会被copy到堆上
    • 栈上的__block变量的__forwarding指向堆上的地址,堆上的__block变量的__forwarding指向自己
    • 保证无论block是在栈上还是在堆上,外部代码在什么地方访问,都能通过__forwarding->localVal找到正确的变量 考虑以下代码 ```objc
  • (void)testBlock { __block CGFloat localVal = 1; __block LabObject *object = [[LabObject alloc] init]; dispatch_block_t block = ^{ localVal = 2; object = nil; };

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ block(); }); } ```

  • block是一个自动变量,作用域结束后将会被释放,但是当加入GCD的队列后,block会被copy到堆上
  • 实际上ARC语境下,block被创建的时候就被__strong修饰,只要hold了变量就会被copy到堆上
  • block_byref_id_object_copy:当block被copy到堆上会自动调用,保证新的block对象和结构体正确的引用计数
  • block_byref_id_object_dispose:当block或__block变量被销毁自动调用,会进行release,保证内存安全

    1.7 block捕获自动变量指针

    ```objc

  • (void)testBlock { __block CGFloat localVal = 1; CGFloat *pointVal = &localVal;

    dispatch_block_t block = ^{ localVal = 2; object = nil; (*pointVal) = 1; }; }

struct __LabObject__testBlock_block_impl_0 { struct __block_impl impl; struct __LabObject__testBlock_block_desc_0* Desc; CGFloat *pointVal; __Block_byref_localVal_0 *localVal; // by ref __Block_byref_object_1 *object; // by ref __LabObject__testBlock_block_impl_0(void *fp, struct __LabObject__testBlock_block_desc_0 *desc, CGFloat *_pointVal, __Block_byref_localVal_0 *_localVal, __Block_byref_object_1 *_object, int flags=0) : pointVal(_pointVal), localVal(_localVal->__forwarding), object(_object->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; ```

  • 捕获的就是指针,可以修改,但是会出现意外情况,如果block被copy到堆上,比如赋值给copy语义的ivar,一旦超出作用域pointVal将会被释放,之后使用的结果将会是野指针