Block实验
前言
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将会被释放,之后使用的结果将会是野指针