目录
块,封装了函数调用以及调用环境的 OC 对象,
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 返回值类型(^block变量名)(参数1类型,参数2类型,...)
void(^block)(void);
// ^返回值类型(参数1,参数2,...){};
// 1.无返回值,无参数
void(^block1)(void) = ^{
};
// 2.无返回值,有参数
void(^block2)(int) = ^(int a){
};
// 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
int(^block3)(void) = ^int{
return 3;
};
// 以上Block的定义也可以这样写:
int(^block4)(void) = ^{
return 3;
};
// 4.有返回值,有参数
int(^block5)(int) = ^int(int a){
return 3 * a;
};
// 1.无返回值,无参数
block1();
// 2.有返回值,有参数
int a = block5(2);
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
isa
指针;通过 Clang 将以下 Block 代码转换为 C++ 代码,来分析 Block 的底层实现。
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"调用了block");
};
block();
}
return 0;
}
__main_block_impl_0
结构体对象,其中有__block_impl
和__main_block_desc_0
两个结构体对象成员。
main:表示 block 所在的函数
block:表示这个一个 block
impl:表示实现(implementation)
0:表示这是该函数中的第一个 block__main_block_func_0
结构体封装了 block 里的代码;__block_impl
结构体才是真正定义 block 的结构,其中的FuncPtr
指针指向__main_block_func_0
;__main_block_desc_0
是 block 的描述对象,存储着 block 的内存大小等;__main_block_impl_0()
构造函数,并且给它传了两个参数__main_block_func_0
和&__main_block_desc_0_DATA
。拿到函数的返回值,再取返回值的地址&__main_block_impl_0
,把这个地址赋值给 block 变量。__main_block_impl_0
中的__block_impl
中的FuncPtr
拿到函数地址,直接调用。// main.cpp
struct __main_block_impl_0 {
struct __block_impl impl; // block的结构体
struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等
/* 构造函数
** 返回值:__main_block_impl_0 结构体
** 参数一:__main_block_func_0 结构体
** 参数二:__main_block_desc_0 结构体的地址
** 参数三:flags 标识位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}
struct __block_impl {
void *isa; // block的类型
int Flags; // 标识位
int Reserved; //
void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
** void(^block)(void) = ^{
NSLog(@"调用了block");
};
** 定义block的本质:
** 调用__main_block_impl_0()构造函数
** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
** __main_block_func_0 封装了block里的代码
** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
** 把这个地址赋值给 block
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
));
/*
** block();
** 调用block的本质:
** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制。
不会捕获
到 block 内部,访问方式为直接访问
;会捕获
到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递
;会捕获
到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递
;连同它的所有权修饰符一起捕获
。
auto 自动变量:我们定义出来的变量,默认都是 auto 类型,只是省略了。
auto int age = 10;
auto 类型的局部变量会捕获
到 block 内部,访问方式为值传递
。
通过 Clang 将以下代码转换为 C++ 代码:
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
__main_block_impl_0
对象内部会生成一个相同的age
变量;__main_block_impl_0()
构造函数多了个参数,用来捕获访问的外面的age
变量的值
,将它赋值给__main_block_impl_0
对象内部的age
变量。struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}
......
int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是值传递
,我们修改外部的age
变量的值,不会影响到 block 内部的age
变量。
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 10
static 类型的局部变量会捕获
到 block 内部,访问方式为指针传递
。
通过 Clang 将以下代码转换为 C++ 代码:
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
__main_block_impl_0
对象内部会生成一个相同类型的age
指针;__main_block_impl_0()
构造函数多了个参数,用来捕获访问的外面的age
变量的地址
,将它赋值给__main_block_impl_0
对象内部的age
指针。struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_a4bc7d_mi_0,(*age));
}
......
static int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是指针传递
,我们修改外部的age
变量的值,会影响到 block 内部的age
变量。
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 20
全局变量不会捕获
到 block 内部,访问方式为直接访问
。
通过 Clang 将以下代码转换为 C++ 代码:
int _age = 10;
static int _height = 20;
......
void(^block)(void) = ^{
NSLog(@"%d,%d",_age,_height);
};
block();
__main_block_impl_0
对象内并没有生成对应的变量,也就是说全局变量没有捕获到 block 内部,而是直接访问。int _age = 10;
static int _height = 20;
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_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_12efa5_mi_0,_age,_height);
}
......
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);
当 block 内部访问了对象类型的 auto 变量时:
__main_block_copy_0
,函数名命名规范同__main_block_impl_0
)
dispose(__main_block_dispose_0
)
② 会调用 block 内部的 copy 函数
③ copy 函数内部会调用_Block_object_assign
函数
④ _Block_object_assign
函数会根据 auto 变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用_Block_object_dispose
函数
③ _Block_object_dispose
函数会自动释放引用的 auto 变量(release)函数 | 调用时机 |
---|---|
copy 函数 | 栈上的 block 复制到堆时 |
dispose 函数 | 堆上的 block 被废弃时 |
如下代码,block 保存在堆中,当执行完作用域2的时候,Person 对象并没有被释放,而是在执行完作用域1的时候释放,说明 block 内部对 Person 对象产生了强引用。
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { //作用域1
MyBlock block;
{ //作用域2
Person *p = [Person new];
p.name = @"zhangsan";
block = ^{
NSLog(@"%@",p.name);
};
}
NSLog(@"-----");
}
return 0;
}
// -----
// Person-dealloc
通过 Clang 将以上代码转换为 C++ 代码:
// 弱引用需要运行时的支持,所以需要加上 -fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
__main_block_impl_0
中生成了一个Person *__strong p
指针,指向外面的 person 对象,且是强引用。
typedef void(*MyBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong p; // 强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__strong p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 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;
MyBlock block;
{
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_0);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_2);
}
return 0;
}
添加了__weak
修饰后,当执行完作用域2的时候,Person 对象就被被释放了。
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { //作用域1
MyBlock block;
{ //作用域2
__weak Person *p = [Person new];
p.name = @"zhangsan";
block = ^{
NSLog(@"%@",p.name);
};
}
NSLog(@"-----");
}
return 0;
}
// Person-dealloc
// -----
同样的,通过 Clang 将以上代码转换为 C++ 代码。
__main_block_impl_0
中生成了一个Person *__weak p
指针,指向外面的 person 对象,且是弱引用。
说明当 block 内部 访问了对象类型的 auto 变量时,如果 block 被拷贝到堆上,会连同对象的所有权修饰符一起捕获。
......
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak p; //弱引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__weak p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_c61841_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
......
默认情况下 block 是不能修改外面的 auto 变量的,解决办法?
struct __Block_byref_age_0
(byref:按地址传递));使用示例
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"block-%d",age);
};
block();
NSLog(@"%d",age);
// block-20
// 20
通过 Clang 将以上代码转换为 C++ 代码。
__Block_byref_age_0
对象;age = 20;
的赋值过程为:通过 block 结构体里的(__Block_byref_age_0
)类型的 age 指针,找到__Block_byref_age_0
结构体的内存(即被 __block 包装成对象的内存),把__Block_byref_age_0
结构体里的 age 变量的值改为20。struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_1,(age.__forwarding->age));
}
return 0;
}
__main_block_copy_0
,函数名命名规范同__main_block_impl_0
)
dispose(__main_block_dispose_0
)
② 会调用 block 内部的 copy 函数
③ copy 函数内部会调用_Block_object_assign
函数
④ _Block_object_assign
函数会对 __block 变量形成强引用(retain)_Block_object_dispose
函数
③ _Block_object_dispose
函数会自动释放引用的 __block 变量(release)
__forwarding
指针存在的意义?__forwarding
指针拿到 age 变量的值,而不直接 age 结构体拿到 age 变量的值呢?__block 的__forwarding
是指向自己本身的指针,为了不论在任何内存位置,都可以顺利的访问同一个 __block 变量。
__forwarding
;__forwarding
指针指向自己,这样通过__forwarding
取到结构体中的 age 给它赋值没有问题;__forwarding
指针会指向 copy 到堆上的 _block 变量结构体,而堆上的__forwarding
指向自己;这样不管我们访问的是栈上还是堆上的 __block 变量结构体,只要是通过__forwarding
指针访问,都是访问到堆上的 __block 变量结构体;给 age 赋值,就肯定会赋值给堆上的那个 __block 变量中的 age。
对象类型的 auto 变量(假设变量名叫做p) | __block 变量(假设变量名叫做a) |
---|---|
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); | _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); |
_Block_object_assign函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 | _Block_object_assign函数会对 __block 变量形成强引用(retain) |
对象类型的 auto 变量(假设变量名叫做p) | __block 变量(假设变量名叫做a) |
---|---|
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); | _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); |
__Block_byref_object_0
即 __block 变量内部会新增两个函数:
copy(__Block_byref_id_object_copy
)
dispose(__Block_byref_id_object_dispose
)
② 会调用 __block 变量内部的 copy 函数
③ copy 函数内部会调用_Block_object_assign
函数
④ _Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于 ARC 时会 retain,MRC 时不会 retain)_Block_object_dispose
函数
③ _Block_object_dispose
函数会自动释放指向的对象(release)int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block NSObject *object = [[NSObject alloc] init];
void(^block)(void) = ^{
object = [[NSObject alloc] init];
};
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
struct __Block_byref_object_0 {
void *__isa;
__Block_byref_object_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // copy
void (*__Block_byref_id_object_dispose)(void*); // dispose
NSObject *__strong object;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_object_0 *object; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_object_0 *_object, int flags=0) : object(_object->__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_object_0 *object = __cself->object; // bound by ref
(object->__forwarding->object) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->object, (void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->object, 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_object_0 object = {
(void*)0,
(__Block_byref_object_0 *)&object,
33554432,
sizeof(__Block_byref_object_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// __block 对象结构体的地址+40个字节,即为结构体中 object 对象的地址
_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);
}
注意:在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题。
示例(MRC)
// 对象类型的捕获,连同所有权修饰符一起捕获,所以 block 对 person 强引用
HTPerson *person = [[HTPerson alloc] init];
void(^block)(void) = [^{
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
// 0x10053da30
// -[HTPerson dealloc]
// __block 修饰的对象类型的捕获,MRC 下在 block 内部不会对该对象进行 retain 操作
__block HTPerson *person = [[HTPerson alloc] init];
void(^block)(void) = [^{
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
// -[HTPerson dealloc]
// 0x1007337d0
block 有 3 种类型,可以通过调用 class 方法或者 isa 指针 查看具体类型,最终都是继承自 NSBlock 类型。
block类型 | 描述 | 环境 |
---|---|---|
__ NSGlobalBlock __( _NSConcreteGlobalBlock ) | 全局block,保存在数据段 | 没有访问auto变量 |
__ NSStackBlock __( _NSConcreteStackBlock ) | 栈block,保存在栈区 | 访问了auto变量 |
__ NSMallocBlock __( _NSConcreteMallocBlock ) | 堆block,保存在堆区 | __ NSStackBlock __调用了copy |
打印各种 block 的类型,以及遍历 block 的父类类型,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block1)(void) = ^{
NSLog(@"hello");
};
/* block2
** 在ARC下会自动copy,从栈复制到堆,所以为__NSMallocBlock__类型
** 在MRC下为__NSStackBlock__类型,需要手动调用copy方法才会变为__NSMallocBlock__类型
** 同时,在不需要该block的时候需要手动调用release方法
*/
int age = 10;
void(^block2)(void) = ^{
NSLog(@"%d",age);
};
NSLog(@"%@,%@,%@", [block1 class], [block2 class], [^{
NSLog(@"%d",age);
} class]);
Class class = [block1 class];
while (class) {
NSLog(@"%@",class);
class = [class superclass];
}
}
return 0;
}
// __NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject
每一种类型的 block 调用 copy 后的结果如下所示:
block类型 | 副本源的配置存储区 | 复制效果 |
---|---|---|
_NSConcreteGlobalBlock | 程序的数据段区 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
以下是在 MRC 环境下,block 类型为__NSStackBlock__
。
当 test() 函数执行完毕,栈上的东西可能会被销毁,数据就会变成垃圾数据。尽管 block 还能正常调用,但是输出的 age 的值发生了错乱。
void (^block)(void);
void test()
{
// __NSStackBlock__
int age = 10;
block = ^{
NSLog(@"%d", age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
// 272632936
解决办法:调用copy
方法,将栈 block 复制到堆。
void (^block)(void);
void test()
{
// __NSMallocBlock__
int age = 10;
block = [^{
NSLog(@"%d", age);
} copy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
[block release];
}
return 0;
}
// 10
在 ARC
环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下几种情况:
__strong
指针时;usingBlock
的方法参数时;strong
或者copy
都会对 block 进行强引用,都会自动将 block 从栈 copy 到堆上;copy
,这样 MRC 和 ARC 下一致。// MRC
@property (nonatomic, copy) void(^block)(void);
// ARC
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, strong) void(^block)(void);
__block
的话,在 ARC 下可能会产生循环引用(MRC 则不会),在 ARC 下可以通过断环的方式去解除循环引用。但是有一个弊端,如果该 block 一直得不到调用,循环引用就一直存在。__weak
或者__unsafe_unretained
解决: __weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
注意:
__unsafe_unretained
会产生悬垂指针。
__block
解决(必须要调用 block):
缺点:必须要调用 block,而且 block 里要将指针置为 nil。如果一直不调用 block,对象就会一直保存在内存中,造成内存泄漏。 __block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
weakSelf = nil;
};
self.block();
__unsafe_unretained
解决:同 ARC__block
解决(在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题) __block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
封装了函数调用以及调用环境的 OC 对象。
block 一旦没有进行 copy 操作,就不会在堆上。 使用注意:循环引用问题。
不需要。
block 的变量捕获机制,是为了保证 block 内部能够正常访问外部的变量。
不会捕获
到 block 内部,访问方式为直接访问
;会捕获
到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递
;指针传递
;连同它的所有权修饰符一起捕获
。会捕获。
OC 方法都有两个隐式参数,方法调用者self
和方法名_cmd
。
参数也是一种局部变量。
会捕获。
不是将_name
变量进行捕获,而是直接将self
捕获到 block 内部,因为_name
是 Person 类的成员变量,_name
来自当前的对象/方法调用者self(self->_name)
。
如果使用self.name
即调用self
的getter
方法,即给self
对象发送一条消息,那还是要访问到self
。self
是局部变量,不是全局变量,所以self
会捕获到 block 内部。
如果没有将 block 从栈上 copy 到堆上,那我们访问栈上的 block 的话,可能会由于变量作用域结束导致栈上的 block 以及 __block 变量被销毁,而造成内存崩溃。或者数据可能会变成垃圾数据,尽管将来 block 还能正常调用,但是它捕获的变量的值已经错乱了。 解决办法:将 block 的内存放堆里,意味着它就不会自动销毁,而是由我们程序员来决定什么时候销毁它。
在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题。
见 3.5.4 __block 的 __forwarding 指针。
block 对于对象类型的局部变量连同所有权修饰符一起截获,所以如果我们在外部定义的对象是 __weak 所有权修饰符的,那么在 block 中所产生的结构体里所持有的变量也是 __weak 类型的。
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%d",strongSelf->age);
};
__block int multiplier = 6;
int (^block)(int) = ^(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"%d",block(2));
// 8
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
weakSelf = nil;
};
self.block();
缺点:必须要调用 block,而且 block 里要将指针置为 nil。如果一直不调用 block,对象就会一直保存在内存中,造成内存泄漏。