iOS开发中,我们经常使用block,尤其在一些异步需要回调的场景,通过向方法传递一个block参数,在异步操作执行完成之后,回调这个block。
block的基本使用
我们常常在文件头部声明block的类型,比如
typedef void(^blkTest)(void);
这里声明了一个没有返回值,没有参数的block,其后如果要使用block的话,可以声明一个block变量,并且调用它。
blkTest blk = ^(){
NSLog(@"block test");
};
blk();
block可以作为方法的返回值,也可以作为方法的参数。在方法的内部,可以生成block类型的变量然后执行它。
block的类型
为了查看block的类型,我们可以调用superclass方法获取它的实际类型。
NSLog(@"%@", [[[blk class] superclass] superclass]);
控制台打印的结果为NSObject,这也验证了block实际为NSObject类型,它是一个对象。
block的实现
为了探究block的底层实现,我们可以使用clang编译器把Objective-C代码转换为C++代码。先写好Objective-C代码,新建一个文件命名为blockTest.m,写入以下代码
#import <Foundation/Foundation.h>
typedef void(^blkTest)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
blkTest blk = ^(){
NSLog(@"block test");
};
blk();
}
return 0;
}
使用clang命令转换为C++代码
clang -rewrite-objc blockTest.m
当前目录下生成了blockTest.cpp文件,打开这个文件,可以发现block的实现代码。
typedef void(*blkTest)(void);
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_6ba139_mi_0);
}
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;
blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->__main_block_func_0)((__block_impl *)blk);
}
return 0;
}
可以发现有三个结构体,分别为__block_impl,__main_block_impl_0,
__main_block_desc_0。__main_block_impl_0持有了
__block_impl和__main_block_desc_0,__main_block_impl_0实际上是block的实现方式。
__block_impl里面保存了isa指针和FuncPtr函数指针,block的具体实现被封装成了函数__main_block_func_0,这个__main_block_func_0会传入__main_block_impl_0结构体用于初始化。
在调用block的时候,实际执行的就是函数指针FuncPtr,也就是
__main_block_func_0
block的具体实现基本就清楚了,通过函数指针封装block的具体实现,并把函数指针传入到__main_block_impl_0结构体用于初始化。block的执行实际是找到这个函数指针并且调用它。
在block的实际使用中,我们常常会捕获变量用于实际处理。修改Objective-C代码,加入变量捕获,看看block的C++实现有什么变化。
#import <Foundation/Foundation.h>
typedef void(^blkTest)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 1;
blkTest blk = ^(){
NSLog(@"%d", a);
NSLog(@"block test");
};
blk();
}
return 0;
}
代码中多了变量a,block中捕获并且打印了变量a的值。再次使用clang命令查看C++代码实现。
clang -rewrite-objc blockTest.m
在生成的C++代码中,可以发现有了一些变化。主要是__main_block_impl_0中多了int a ,__main_block_func_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_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_0, a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_1);
}
可以发现,存在变量捕获的情况下,捕获的变量会传递到block结构体中。注意这里传递的值,而不是引用。捕获变量的情况下,如果修改了变量值,Xcode会报错。如果想修改捕获的变量的值,需要在变量前面加上__block声明,block前面是两个下划线。加了__block声明后,block的C++实现有什么变化呢?
#import <Foundation/Foundation.h>
typedef void(^blkTest)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 1;
blkTest blk = ^(){
a = 2;
NSLog(@"%d", a);
NSLog(@"block test");
};
blk();
}
return 0;
}
接着使用clang命令查看C++实现。
clang -rewrite-objc blockTest.m
在生成的C++代码中,发生了一些变化。
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
(a->__forwarding->a) = 2;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_0, (a->__forwarding->a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_1);
}
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), 1};
blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
可以发现多了__Block_byref_a_0结构体,__main_block_impl_0的初始化参数也多了这个结构体。变量a转换成结构体__Block_byref_a_0,并且传入__main_block_impl_0结构体。注意main函数的中代码,传递的是变量a的引用,而不是a的值,这样block就可以修改了变量a的值了。
blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
block的内存区域
在iOS的内存分布中,有栈,堆,BSS,全局变量区,代码段。block的分类包括__NSMallocBlock__,NSGlobalBlock__和__NSStackBlock。
ARC环境下,对于没有捕获变量的block而言,默认是NSGlobalBlock类型,并且方法到全局变量区。对于捕获了变量的block而言,实际执行时会自动把NSStackBlock从栈上拷贝到堆上,变为NSMallocBlock类型,存储位置在堆上。
block循环引用问题
Objective-C的内存使用了计数的方式管理内存,如果block和变量相互持有的话,会产生循环引用,这个时候可以使用weak解除循环引用。
__weak typeof (self) weakSelf = self;
在block中使用weakSelf不会导致循环引用的发生。