block
的数据结构
先来一个最简单的block
,看看这个block
到底执行了什么
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"this is block");
};
block();
}
return 0;
}
cd
到main.m
的目录下,执行:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
我们可以看到上述的代码,转换为c++
代码为:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_6ab938_mi_0);
}
// main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
block
声明简化后为:
*block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
全局搜索__main_block_impl_0
,我们在结构体__main_block_impl_0
里面找到了同名构造函数。
结论:block
是包含isa
指针的对象。
block
的类型
通过打印class
及superclass
可以看出继承关系如下所示,class
为__NSGlobalBlock__
当我们传递一个局部变量进
block
的时候,发现class
发生改变,变成__NSMallocBlock__
将
ARC
环境转成MRC
之后,
执行相同的代码,发现
block
的class
变成__NSStackBlock__
结论:block是对象类型,有完整的继承链,当声明block
时,会转化成__NSGlobalBlock__
、__NSStackBlock__
、__NSMallocBlock__
三种类型。
1、创建一个不包含外部变量的block
的时候,则该block
在全局区,是__NSGlobalBlock__
类型。
2、block
包含局部变量的时候,MRC
环境下,则该block
在栈区,是__NSStackBlock__
类型。
3、block
包含局部变量的时候,ARC
环境下,则该block
被拷贝到堆区,是__NSMallocBlock__
类型。
ARC
环境下成为__NSMallocBlock__
条件:有需要自己处理对象生命周期的时候,会将栈区的__NSStackBlock__
类型copy
到堆区,生成__NSMallocBlock__
类型。
经典问题:如下代码打印结果是什么,以及为什么(涉及到__block
)
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void(^block)(void) = ^{
NSLog(@"a = %d" , a);
};
a = 20;
block();
}
return 0;
}
打印结果为10
,现在开始解析原因,将上述代码重新编译成C++
代码之后,代码为:
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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_cf236d_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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
在创建block
的时候,__main_block_impl_0
结构中,会创建一个int a;
,在block
执行构建函数的时候,将外面的a
作为参数传进去,再对__main_block_impl_0
结构中,创建的int a;
参数进行赋值操作,此时,block
内部的a
,其初始值即为外面a
的值,为10
,后面执行a = 20;
的操作实际上是对外面的a
的操作,并没有影响到内部的结果,所以打印结果为:a = 10
,打印的实际上是捕获到的值。
那么我们如何处理a
值不变的问题呢?
方法一:使用局部静态变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int a = 10;
void(^block)(void) = ^{
NSLog(@"a = %d" , a);
};
a = 20;
block();
}
return 0;
}
底层代码实现原理如下:
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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_8dcf0c_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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
构建函数的时候,传递了static int a = 10;
的地址,block
内部用一个int *a
接收地址,所以调用的时候,实际上是捕获静态局部变量的地址,所以可以值会同步打印。【局部静态变量,block
获取指针地址】
方法二:使用全局变量
static int a = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"a = %d" , a);
};
a = 20;
block();
}
return 0;
}
底层代码实现原理如下:
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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_7445b2_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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
因为是全局变量,所以直接调用全局变量。
方法三(重要):使用__block
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void(^block)(void) = ^{
NSLog(@"a = %d" , a);
};
a = 20;
block();
}
return 0;
}
底层代码实现原理如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_bc1a46_mi_0 , (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
当我们执行__block int a = 10;
的时候,实际底层代码转换为:
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
__main_block_impl_0
中会出现一个__Block_byref_a_0 *a;
,是包含isa
指针的对象,__Block_byref_a_0
中包含int a;
,我们在__main_block_desc_0
结构中,多出了__main_block_copy_0
和__main_block_dispose_0
函数,
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
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*/);}
copy
操作实际上是调用_Block_object_assign
函数,进行copy(拷贝)
操作,dispose
操作实际上是调用_Block_object_dispose
函数,进行dispose(销毁)
操作。
结论:当__block作用于局部变量时,会将局部变量封装成一个__Block_byref_XXX_0
类型的对象,局部变量的值复制给了__Block_byref_XXX_0
类型对象中的同名成员变量
中,此时暴露在外面的__block int a = 10;
实际上已经封装成了一个对象,当我们对a
进行赋值操作的时候,实际上执行的是(a.__forwarding->a) = 20;
操作,__forwarding
存储的地址就是__Block_byref_a_0 *a
的地址,目的是为了保证栈区的block copy到堆区的时候依然可以找到相对应的地址
。
block
循环引用
申明一个BlockObject
对象
@interface BlockObject : NSObject
@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);
@end
@implementation BlockObject
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
// main.m
#import
#import "BlockObject.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
BlockObject *objc = [[BlockObject alloc] init];
objc.num = 10;
objc.block = ^{
NSLog(@"%d", objc.num);
};
}
NSLog(@"-----------");
return 0;
}
运行结果显示,没有执行dealloc
里面的方法,造成了循环引用。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
BlockObject *objc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *_objc, int flags=0) : objc(_objc) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
BlockObject *objc = __cself->objc; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)objc, sel_registerName("num")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
BlockObject *objc = ((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockObject"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)objc, sel_registerName("setNum:"), 10);
((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)objc, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, objc, 570425344)));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_1);
return 0;
}
当block
里面包含对象类型时,__main_block_impl_0
包含了BlockObject *objc;
对象,__main_block_copy_0
对objc
对象执行了copy
操作,objc
的retain
+1。当执行__main_block_dispose_0
的时候,则出现:objc
对象中引用了block
属性,在block
对象中,有了一个被强引用的objc
属性,当执行dispose
的时候,则会发生互相等待被销毁的循环引用问题。
解决方法1:使用__weak
int main(int argc, const char * argv[]) {
@autoreleasepool {
BlockObject *objc = [[BlockObject alloc] init];
objc.num = 10;
__weak typeof(objc) weakObjc = objc;
objc.block = ^{
NSLog(@"%d", weakObjc.num);
};
}
NSLog(@"-----------");
return 0;
}
由于__weak
会用到runtime
相关的东西,OC
转为C++
可以使用如下方式:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
可以看到如下数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
BlockObject *__weak weakObjc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *__weak _weakObjc, int flags=0) : weakObjc(_weakObjc) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__weak
实际上是对block
里面的对象执行了weak操作
,结构为:BlockObject *__weak weakObjc;
。__main_block_copy_0
对objc
对象执行了copy
操作,实际上也只是进行了一次weak
引用,所以不会造成循环引用。
解决方法2:使用__block
// BlockObject.h
@interface BlockObject : NSObject
@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);
- (void)test;
@end
// BlockObject.m
@implementation BlockObject
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (void)test {
__block id blockSelf = self;
self.block = ^{
NSLog(@"%p", blockSelf);
blockSelf = nil;
};
self.block();
}
@end
// main.m
#import
#import "BlockObject.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
BlockObject *objc = [[BlockObject alloc] init];
objc.num = 10;
[objc test];
}
NSLog(@"-----------");
return 0;
}
这个方法一般用的比较少,实际上实现原理是形成三角关系,由于__block
创建了一个__Block_byref_XXX_0
类型的对象,block
内部执行了blockSelf = nil;
的操作实际上就是切断三角关系,达到去除循环引用的效果。