Block回顾
记得上次分析Block是一个月前了,昨天遇到个问题,真是搞了好久才搞定。所以觉定把blcok再次好好学习一下。
clang
在开始之前,有个终端命令需要介绍一下,clang。不清楚的问下度娘,平时开发中也比较少用到,大部分是位了分析稍微偏底层一些的代码。
我这里只用到了将OC转为C++代码的命令
clang -rewrite-objc
clang /Users/kingcodexl/Documents/Learn/Learn/BlockAnalysis/BlockAnalysis/main.m -rewrite-objc
随后会生成一个main.cpp的文件。打开这个文件,我靠,足足十万多行的代码。幸好可以通过查找的方式找到我们需要的。
代码先行:
// block的最为基本的定义
struct __block_impl {
void *isa;//block类型,如全局,栈,堆
int Flags;//??
int Reserved;//变量个数
void *FuncPtr;//执行函数
};
//定义block会转为如下的结构体
struct __main_block_impl_0 {
struct __block_impl impl;//包含block基本的信息
struct __main_block_desc_0* Desc;//包含变量个数与block的size
int val;//变量
//这个想了好久,就当做这个结构体的初始化函数吧
//传入block具体执行的函数,以及block辅助信息,和变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//这个就是真正进行处理的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_41daf1_mi_0, val);
}
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 val = 10;
//这两个函数看起来有点蛋疼,就是将结构体初始化函数赋给一个函数指针
void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val);
//由于已经初始化了结构体,所以已经有可以执行的方法,
//这里执行
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr) }
return 0;
}
为什么Block不能直接更改外部变量
原理在一个函数里面定义结构体,最终会分解为多个函数的执行,这就涉及到了变量的作用域,其实我认为可以通过指针来达到(默认值是copy了一份),但是考虑到了大部分情况block用作回调,很有可能在主函数中的局部变量已经被自动释放了,而block分解出来的函数里面用指针指向,会造成问题。所以呢,就不能更改外部的变量。
最终原因还是一个作用域导致的
加上__block之后的世界
代码先行
__block NSInteger val = 0;
void (^block)(void) = ^{
val = 1;
};
block();
NSLog(@”val = %ld”, val);
蛋疼
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
NSInteger val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0 (struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/BLOCK_FIELD_IS_BYREF/);
}
static void __main_block_dispose_0 (struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 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[]) {
{ __AtAutoreleasePool __autoreleasepool;
attribute((blocks(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
void (block)(void) = (void ()())&__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 )&val, 570425344);
((void ()(__block_impl ))((__block_impl )block)->FuncPtr)((__block_impl )block);
NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_d7fc4b_mi_0, (val.__forwarding->val));
}
return 0;
}
为了清楚一点,把里面变换的了的重要部分提出来
struct __Block_byref_val_0 {
void *__isa;
//注意这里包含了自身本身
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
NSInteger val;
};
我们发现由__block修饰的变量变成了一个__Block_byref_val_0结构体类型的实例
static void __main_block_func_0 (struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
((void ()(__block_impl ))((__block_impl )block)->FuncPtr)((__block_impl )block);
这里的__forwarding作用就是就将__block修饰的局部变量,移动到堆上面,
NSConcreteMallocBlock就登场了,Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将分配到栈上的Block复制到堆上,这样但栈上的Block超过它原本作用域时,堆上的Block还可以继续存在。
只要栈上的_block变量的成员变量__forwarding指向堆上的实例,我们就能够正确访问。
当一个Block被复制到堆上时,与之相关的__block变量也会被复制到堆上,此时堆上的Block持有相应堆上的__block变量。当堆上的__block变量没有持有者时,它才会被废弃。(这里的思考方式和objc引用计数内存管理完全相同。)
而在栈上的__block变量被复制到堆上之后,会将成员变量__forwarding的值替换为堆上的__block变量的地址。
补充:
_NSConcreteMallocBlock就登场了,Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将分配到栈上的Block复制到堆上,这样但栈上的Block超过它原本作用域时,堆上的Block还可以继续存在。
复制到堆上的Block,它的结构体成员变量isa将变为:
1
impl.isa = &_NSConcreteMallocBlock;
而_block变量中结构体成员__forwarding就在此时保证了从栈上复制到堆上能够正确访问__block变量。在这种情况下,只要栈上的_block变量的成员变量__forwarding指向堆上的实例,我们就能够正确访问。
我们一般可以使用copy方法手动将 Block 或者 __block变量从栈复制到堆上。比如我们把Block做为类的属性访问时,我们一般把该属性设为copy。有些情况下我们可以不用手动复制,比如Cocoa框架中使用含有usingBlock方法名的方法时,或者GCD的API中传递Block时。
当一个Block被复制到堆上时,与之相关的__block变量也会被复制到堆上,此时堆上的Block持有相应堆上的__block变量。当堆上的__block变量没有持有者时,它才会被废弃。(这里的思考方式和objc引用计数内存管理完全相同。)
而在栈上的__block变量被复制到堆上之后,会将成员变量__forwarding的值替换为堆上的__block变量的地址。这个时候我们可以通过以下代码访问:
1
val.__forwarding->val