Block
Block
block的本质是什么?是 oc 对象(包含isa指针), 是函数及其运行环境、上下文的封装。 常用于
使用
-
作为变量
int (^sum) (int, int); // 定义一个 Block 变量 sum // 给 Block 变量赋值 // 一般 返回值省略:sum = ^(int a,int b)… sum = ^int (int a,int b){ return a+b; }; // 赋值语句最后有 分号 int a = sum(10,20); // 调用 Block 变量
-
作为属性
// 1. 给 Calculate 类型 sum变量 赋值「下定义」 typedef int (^Calculate)(int, int); // calculate就是类型名 Calculate sum = ^(int a,int b){ return a+b; }; int a = sum(10,20); // 调用 sum变量 // 2. 作为对象的属性声明,copy 后 block 会转移到堆中和对象一起 @property (nonatomic, copy) Calculate sum; // 使用 typedef @property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef // 声明,类外 self.sum = ^(int a,int b){ return a+b; }; // 调用,类内 int a = self.sum(10,20);
-
作为方法参数
// ---- 无参数传递的 Block --------------------------- // 实现 - (CGFloat)testTimeConsume:(void(^)())middleBlock { // 执行前记录下当前的时间 CFTimeInterval startTime = CACurrentMediaTime(); middleBlock(); // 执行后记录下当前的时间 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - startTime; } // 调用 [self testTimeConsume:^{ // 放入 block 中的代码 }]; // ---- 有参数传递的 Block --------------------------- // 实现 - (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock { // 执行前记录下当前的时间 CFTimeInterval startTime = CACurrentMediaTime(); NSString *name = @"有参数"; middleBlock(name); // 执行后记录下当前的时间 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - startTime; } // 调用 [self testTimeConsume:^(NSString *name) { // 放入 block 中的代码,可以使用参数 name // 参数 name 是实现代码中传入的,在调用时只能使用,不能传值 }];
-
用于回调
Block 实现
搞清楚Block实现原理,只要搞清楚Block的函数调用、和变量捕获的逻辑,我们可以通过clang将oc文件转译成cpp文件来查看block的c语言实现:
新建文件 main.m:
int main()
{
int age=10;
void (^Block)(void) = ^{
printf("age:%d",age);
};
age = 20;
Block();
}
使用命令:clang -rewrite-objc main.m 生成main.cpp,在main函数里面可以看到:
int main()
{
int age=10;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
进一步可以查看__main_block_func_0等结构体的定义:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
printf("age:%d",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;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
从上面可以看到block赋值,其实是用 main_block_func_0(函数指针)、 main_block_desc_0_DATA(描述说明)、age(外部变量)生成一个__main_block_impl_0结构的实例blk,这个blk即是我们在oc文件里面定义的block的对象blk。
block的调用,实际上则是调用blk的FuncPtr函数,其入参为blk本身。
以上为一个最简单的block的实现源码解析,基于这个简单的block可以总结如下:
- block调用实际上是函数指针的调用
- block的普通传值,实际上作为成员变量存储在block里面
__block
先来几个关于block捕获变量知识点:
Q:为什么block对auto和static变量捕获有差异?
auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可
Q:block对全局变量的捕获方式是?
block不需要对全局变量捕获,都是直接采用取值的
Q:为什么局部变量需要捕获?
考虑作用域的问题,需要跨函数访问,就需要捕获
Q:block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
Q:block里访问self是否会捕获?
会,self是当调用block函数的参数,参数是局部变量,self指向调用者
Q:block里访问成员变量是否会捕获?
会,成员变量的访问其实是self->xx
,先捕获self,再通过self访问里面的成员变量
下面依然通过源码转换方式,探究下__block的实现原理,使用如下源码:
int main()
{
__block int age=10;
void (^blk)(void) = ^{
printf("age:%d",age);
};
age = 20;
blk();
}
转换后可以看到对应几个代码块:
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
(age.__forwarding->age) = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
可以看到,block的调用依然没有变化,而变量age则从之前的简单传值,变成了一个__Block_byref_age_0变量传入(传址),block的初始化也有了变化,先展开 Block_byref_age_0:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
这里可以看到,Block_byref_age_0实际同时保存了age的值(int age)和地址(__forwarding,也就是&age),而对age的赋值,则变成
(age.__forwarding->age) = 20;
所以该赋值同时能改变block捕获的age的值了。
以上。
既已览卷至此,何不品评一二: