平时我们在开发中经常会用到block,那么这个block究竟是个什么东东? 然后使用的时候需要注意什么呢? 话不多说,直接进入正题。。。
首先我们写下了一个这个样子的block。。。
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^testBlock)(NSString *) = ^ (NSString *str) {
NSLog(@"----%@----",str);
};
testBlock(@"hello world!");
}
return 0;
}
然后为了探究其原理,我们使用 clang -rewrite-objc main.m
就会看到一个同名的cpp
文件,这个就是我们通过Clang(LLVM编译器)将OC的代码转换成了C++源码。
额。。打开这个文件。。。WTF?? 一共9w多行代码。。。这都是些什么玩意。。细看都是一堆结构体和方法。。继续往下找重点。。。咦,发现我们的main函数的内容被转换成了这个样子:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
void (*testBlock)(NSString *) = ((void (*)(NSString *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, NSString *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, (NSString *)&__NSConstantStringImpl__var_folders_q1_yw_3n1px5bb6019pv_7gmtb00000gn_T_main_b79bfb_mi_1);
}
return 0;
}
我们写的block和block的调用最后被转换成了这个样子。。我们试着搜索被转后的东东。。然后我们发现了这么些结构体
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, NSString *str) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_q1_yw_3n1px5bb6019pv_7gmtb00000gn_T_main_b79bfb_mi_0,str);
}
这是一个看似很普通到结构体,然后我们分别找到内部的结构体。
#define BLOCK_IMPL
struct __block_impl {
void *isa; // 指向所属类的指针
int Flags; // 标志变量
int Reserved; // 是否保留变量
void *FuncPtr; // block执行时调用的函数指针
};
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)};
不难发现__main_block_impl_0
就是block的一个C++的实现,后面的零代表的是main中的第几个block。 __block_impl
也有isa指针,说明block也是一个对象。可以看出:
- __main_block_impl_0的isa指针指向了_NSConcreteStackBlock,
- __main_block_impl_0的FuncPtr指向了函数__main_block_func_0
- __main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。
我们再来看下OC对block的实现:
可以从opensource.apple中的 Block_private.h
中看到这样的结构体:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
// imported variables
};
// revised new layout
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
发现跟用clang命令(LLVM)处理的结果差不太多,只是稍微的有些不同:
* invoke,跟上文的FuncPtr一样,block执行时调用的函数指针,block定义时内部的执行代码都在这个函数中
* Block_descriptor,block的详细描述
copy/dispose,拷贝函数/销毁函数,处理block范围外的变量时使用
总体来说,block就是一个里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体。
常见的block类型
我们从data.c 文件中找到了这几种block,不过前三种只是在GC环境下使用:
_NSConcreteFinalizingBlock
_NSConcreteAutoBlock
_NSConcreteWeakBlockVariable
_NSConcreteGlobalBlock(全局)
_NSConcreteStackBlock(栈)
_NSConcreteMallocBlock(堆)
为了探究为什么在block中,有些变量可以直接修改而有一些需要加上__block之后才可以修改,我们又写了一段这样的代码:
int globalVal = 0;
static int staticGlobalVal = 0;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int autoVal = 0;
static int staticVal = 0;
void (^testBlock)() = ^ () {
globalVal++;
staticGlobalVal++;
//autoVal++;
staticVal++;
NSLog(@"----%d----%d----%d----%d----",globalVal,staticGlobalVal,autoVal,staticVal);
};
testBlock();
}
return 0;
}
我们继续用clang命令进行解析,得到如下代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *staticVal = __cself->staticVal; // bound by copy
int autoVal = __cself->autoVal; // bound by copy
globalVal++;
staticGlobalVal++;
(*staticVal)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_q1_yw_3n1px5bb6019pv_7gmtb00000gn_T_main_95d0b8_mi_0,globalVal,staticGlobalVal,autoVal,(*staticVal));
}
首先,我们可以看到,全局变量和静态全局变量的值自增,这个我们不难理解,因为这两个变量是在全局数据存储区,由于作用域很大,block捕获它们进去之后,就会进行自增的操作,Block结束之后,它们的值依旧可以得以保存下来。接下来我们再看自动变量和静态变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticVal, int _autoVal, int flags=0) : staticVal(_staticVal), autoVal(_autoVal) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
这个构造函数中,自动变量和静态变量被捕获为成员变量追加到了构造函数中。但是我们从编译器的注释(bound by copy)来看,静态变量和自动变量都被block捕获进来了,但是自动变量是用
__cself->autoVal
来访问的,也就是说block只捕获了自动变量的值,并没有捕获它的内存地址,所以我们通常无法在block中直接改变它外部的值,而静态变量是捕获到了它的内存地址,我们可以直接修改它的值.
总结,自动变量是以值传递方式传递到Block的构造函数里面去的,Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,而不是内存地址,所以Block内部不能改变自动变量的值。而全局变量,全局静态变量存储在全局区,作用域大,所以可以直接修改;静态变量传递给block的是内存地址,所以也可以修改。也就是说,可以修改block变量值有两种方式,一是传递内存地址,二是改变变量存储区域。
而我们可以通过加__block
关键字来改变其修饰的变量,我们接着而看代码:
struct __Block_byref_autoVal_0 {
void *__isa; // 指向自身的类
__Block_byref_autoVal_0 *__forwarding; // 指向自身类型的__forwarding指针
int __flags; // 标记
int __size; // 大小
int autoVal; // 变量名
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_autoVal_0 *autoVal = __cself->autoVal; // bound by ref
int *staticVal = __cself->staticVal; // bound by copy
globalVal++;
staticGlobalVal++;
(autoVal->__forwarding->autoVal)++;
(*staticVal)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_q1_yw_3n1px5bb6019pv_7gmtb00000gn_T_main_c2f928_mi_0,globalVal,staticGlobalVal,(autoVal->__forwarding->autoVal),(*staticVal));
}
我们可以看出,带有 __block修饰的变量也被转化成了一个结构体__Block_byref_i_0
,__forwarding指针初始化传递的是自己的地址。最后,带__block修饰的自动变量 和 静态变量 就是直接地址访问,所以在Block里面可以直接改变变量的值。
最后更新: 2023年03月25日 22:39:55
本文链接: http://aeronxie.github.io/post/9cbe35e.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!