你要知道的block都在这里
转载请注明出处 //www.greatytc.com/p/b6a675a7a4fa
上一篇文章iOS block探究(一):基础详解介绍了block
的基本原理和使用方法,以及相关修饰符详解。
本文将会深入底层探究block
的本质。
三种block类型
NSGlobalBlock
如果block
不捕获外部变量,那么在ARC环境下就是创建一个全局block
。全局block
存储在全局内存中,不需要在每次调用的时候都在栈中创建,块所使用的整个内存区在编译期已经确定了,因此这种块是一种单例,不需要多次创建。
NSMallocBlock
如果block捕获外部变量,那么在ARC环境下就是创了一个堆区block
。代码中最常用的block
也就是堆区block
,当堆区block
的引用计数为0时也会像普通对象一样被销毁,再也不能使用了。
NSStackBlock
在MRC环境下,默认创建栈区block,一般使用copy
函数拷贝到堆区再使用,否则block
可能会被释放,在ARC环境下一般不考虑。
深入代码理解block
LLVM Block_private.h可以找到苹果关于block
的相关定义。
比较重要的定义代码如下:
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
上述结构体中最重要的就是invoke
变量,从声明中可以看出,这是一个函数指针,指向block
的执行代码,可以认为block
的执行代码是一个匿名函数,在创建block
的时候传递给了invoke
变量。
struct Block_layout
结构体中有一个descriptor
变量,而struct Block_descriptor
比较重要的就是copy
函数和dispose
函数,从命名就可以看出,copy
函数用于捕获变量并持有引用,而dispose
函数就是用于释放捕获的变量。
block
捕获的变量都会存储在结构体struct Block_layout
的后面,对于对象存储的是指针,在invoke
函数执行之前全部读出。
以上就是block
大致的实现方式,可以看出,block
是一种替换函数指针的语法,相比使用函数指针更方法,写法也更便捷。
接下来看一下具体代码的实现。
在进入正题之前,先介绍一个clang
编译器的命令
clang -rewrite-objc main.m
这个命令用于clang
重写.m文件
为.cpp文件
先实现一个最简单的无参数无返回值的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^printBlock)() = ^{
NSLog(@"Hello World");
};
printBlock();
}
return 0;
}
使用上述命令生成.cpp文件
后可以找到如下代码
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_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_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;
void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello World",11};
上述代码中__main_block_func_0
函数就是创建block
时定义的一个函数,当block
执行时就是执行了该函数,这个函数内部是调用了另一个函数,也就是block
里写的执行代码,可以看出,block
实际就是将我们定义的block
又封装了一下,使用起来更方便。
接下来修改代码,让block
捕获一个对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *name = @"Jiaming Chen";
void (^printBlock)() = ^{
NSLog(@"Hello World %@", name);
};
printBlock();
}
return 0;
}
再次使用clang
重写代码后可以看到如下定义
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSString *name;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_name, int flags=0) : name(_name) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSString *name = __cself->name; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_1, name);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->name, 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;
NSString *name = (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_0;
void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, name, 570425344));
((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
在struct __main_block_impl_0
中可以看到里面存储了被捕获的对象,同时在__main_block_func_0
函数中将捕获的对象赋值给了上述结构体变量。并且增加了__main_block_copy_0
函数和__main_block_dispose_0
函数,分别用于持有对象和释放对象。
再看一下__block
的使用会有什么区别。
修改代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSUInteger age = 22;
void (^printBlock)() = ^{
NSLog(@"Hello World %ld", age);
age = 100;
};
printBlock();
}
return 0;
}
重写生成的代码如下:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
NSUInteger 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
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_ea236c_mi_0, (age->__forwarding->age));
(age->__forwarding->age) = 100;
}
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), 22};
void (*printBlock)() = ((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 *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
上述代码可以看出,使用__block
修饰后会生成一个struct __Block_byref_age_0
的结构体,可以看出,__block
修饰后会捕获变量的引用而不是进行值拷贝,这也就是为什么block
内部可以修改__block
修饰的变量以及外部变量修改后会影响block
内部捕获变量的原因了。
备注
由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。