一、基本用法
1.作为变量
//有参数有返回值的block
int(^block)(NSString *name)=^(NSString *name){
NSLog(@"%@",name);
return 100;
}
block(@"zht");
//无参数和返回值的block
void(^block1)(void)=^{
NSLog(@"this is a block");
}
block1();
2.作为属性
//有参数有返回值的block
@property (nonatomic, copy) int (^blockName)(NSString *name);
//无参数和返回值的block
@property (nonatomic, copy) void (^blockName)(void);
3.作为参数
//有参数有返回值的block
-(void)testBlock:(int(^)(NSString *name))complateBlock;
//无参数和返回值的block
-(void)testBlock:(void(^)(void))complateBlock;
4.声明替换 -- typeDef
typedef int(^TestBlock1)(NSString *name);
typedef void(^TestBlock2)(void);
//那上面的代码分别可以这么写
TestBlock1 block1=^(NSString *name){
NSLog(@"%@",name);
return 100;
}
TestBlock2 block2=^{
NSLog(@"this is a block");
}
//有参数有返回值的block
@property (nonatomic, copy) TestBlock1 block1;
//无参数和返回值的block
@property (nonatomic, copy) TestBlock2 block2;
//有参数有返回值的block
-(void)testBlock:(TestBlock1)complateBlock;
//无参数和返回值的block
-(void)testBlock:(TestBlock2)complateBlock;
二、本质
有下面的block代码
void(^block)(int,int) = ^(int a,int b){
NSLog(@"this is a block");
};
block(10,20);
执行命令转化成c++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
转化后上面的两句代码变成
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
精简下
void(*block)(int,int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block, 10, 20);
下面我们看下__main_block_impl_0、__main_block_func_0和__main_block_desc_0_DATA
//block的本质结构
struct __main_block_impl_0 {
struct __block_impl impl;//存储了block的信息
struct __main_block_desc_0* Desc;//里面存储了block占用内存的大小
//相当于block的构造函数 返回一个结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//这里便是了block的类型,我们稍后再说
impl.Flags = flags;
impl.FuncPtr = fp; //存储block代码块生成的函数的地址
Desc = desc; //存储block的所需的内存大小
}
};
//block信息结构体
struct __block_impl {
void *isa; //看到了对象才有的isa指针。
int Flags;
int Reserved;
void *FuncPtr;//代码块生成的函数
};
//定义了__main_block_desc_0结构体,并声明了__main_block_desc_0_DATA结构体变量,变量中存储了block需要多大内存
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)};
//这是是由block的代码块生成的函数。 block的参数也在这里体现出来了
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_main_d39147_mi_0);
}
定义一个block后,底层结构如下图
所以block声明和调用理解如下。
//定义block变量,这里调用__main_block_impl_0的构造函数,拿到返回值(其实就是一个__main_block_impl_0结构体对象),然后取地址复制给block变量
//1.把block中的代码块封装成的__main_block_func_0函数传入__main_block_impl_0的构造函数,最后由__block_impl->FuncPtr存储起来
//2.把{ 0, sizeof(struct __main_block_impl_0)存储到__main_block_impl_0->Desc
void(*block)(int,int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
//调用block,通过__block_impl->FuncPtr取到__main_block_func_0函数,再去执行。
block->FuncPtr(block, 10, 20);
block本质上就是一个OC对象,它内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
三、变量捕获
1.局部auto变量捕获
(1)基本类型
int age = 10;
void(^block)(void) = ^{
NSLog(@"this is a block--%d",age);
};
age = 20;
block();
运行结果
转化成C++后
int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));//这里把age的值传进了__main_block_impl_0结构体中
age = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//block的本质结构 这里多了一个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;
}
};
//这是是由block的代码块生成的函数。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; //取出__main_block_impl_0结构体中的age变量
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_main_5becdd_mi_0,age);
}
我们发现block的底层结构体里面多了一个age成员变量,执行代码void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));,利用__main_block_impl_0的构造函数把age的值传递给了__main_block_impl_0结构体中的age成员变量,注意这时候值是10,执行block代码块是再取出来__main_block_impl_0结构体中的age成员变量的值,所以打印结果是10,不是20。
(2)对象类型
//代码一
__strong Person * p = [[Person alloc] init];
p.name = @"zht";
void(^block)(void) = ^{
NSLog(@"this is a block--%@",p.name);
};
block();
//代码二
__strong Person * p = [[Person alloc] init];
p.name = @"zht";
__weak Person *weakPerson = p;
void(^block)(void) = ^{
NSLog(@"this is a block--%@",weakPerson.name);
};
block();
转化成C++
//这里需要制定arc环境 用到的runtime版本
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 main.m -o main-arm64.cpp
转化后
//代码一
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong p;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//代码二
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们看到__main_block_impl_0中的person的指针类型不一样,一个是__strong 一个是__weak ,这也解释了用__weak解决block循环引用的关键。除此之外__main_block_desc_0结构体也多了两个函数指针,我们稍后介绍这两个函数的作用。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 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}
2.局部static变量捕获
static int age = 10;
void(^block) = ^{
NSLog(@"this is a block--%d",age);
};
age = 20;
block();
运行结果
转化成C++后
static int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));//这里把age的地址值传进了__main_block_impl_0结构体中
age = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//block的本质结构 这里多了一个指向int类型的指针用来存储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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age; //取出__main_block_impl_0结构体中的age内存地址
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_main_849526_mi_0,(*age));
}
这里跟局部auto变量的区别是把age的内存地址传入了block,后面再修改age的值,执行block代码块时根据block内部的age的内存地址取到age最新的值,因此结果是20。
3.全局auto变量和static变量捕获
int age_ = 10;
static int height_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"this is a block--%d---%d",age_,height_);
};
age_ = 20;
height_ = 20;
block();
}
return 0;
}
运行结果
转化成C++后
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //这里根本没有对全局变量进行捕获
age_ = 20;
height_ = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
int age_ = 10;
static int height_ = 10;
//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;
}
};
//执行block代码块也是直接访问的全部变量
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_main_203762_mi_0,age_,height_);
}
这里我们发现block根本不会去捕获全局变量,也根本没有必要去捕获。
4.总结
- 对于
基本数据类型的局部变量截获其值 - 对于
对象类型的局部变量连同所有权修辞符一起截获 - 对于
局部静态变量以指针形式截获 不截获全局变量、静态全局变量
搞个面试题
@implementation Person
-(void)test{
void(^block)(void)=^{
NSLog(@"----%p---",self); //这里的self会不会被block捕获呢?
};
block();
}
@end
转化成C++后
//每个函数都会自动添加两个参数self和_cmd
static void _I_Person_test(Person * self, SEL _cmd) {
void(*block)(void)=((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));//这里我们发现把self捕获了,而且是值传递。说明函数里面的self是局部变量。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
再来个面试题
@interface Person()
@property(nonatomic,copy)NSString *name;
@end
@implementation Person
-(void)test{
void(^block)(void)=^{
NSLog(@"----%p---",_name);//这里会不会对_name进行捕获
};
block();
}
@end
转化成C++后
static void _I_Person_test(Person * self, SEL _cmd) {
void(*block)(void)=((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));//这里我们发现把self捕获了,而且是值传递。说明函数里面的self是局部变量。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_Person_4ad387_mi_0,(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)));//通过self再获取_name
}
这里不会捕获_name,捕获了self局部变量,然后通过self再来获取_name.
四、类型
void(^block)(void) = ^{
NSLog(@"this is a block");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
执行结果
上面我们可以再次证明block本质上是对象。
结论:block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。三种类型分别是:NSGlobalBlock(_NSConcreteGlobalBlock)、NSStackBlock(_NSConcreteStackBlock)、NSMallocBlock(_NSConcreteMallocBlock)。 存储区域如下
执行下面代码,打印一下三个block的类型。
void(^block1)(void) = ^{
NSLog(@"this is a block");
};
int age = 10;
void(^block2)(void) = ^{
NSLog(@"this is a block--%d",age);
};
NSLog(@"%@ %@ %@",[block1 class],[block2 class],[^{
NSLog(@"this is a block--%d",age);
} class]);
转化成C++
//代表block1
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;
}
};
//代表block2
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int age;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//代表最后一个block
struct __main_block_impl_2 {
struct __block_impl impl;
struct __main_block_desc_2* Desc;
int age;
__main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们奇怪的发现转成c++三个block的类型都是_NSConcreteStackBlock。我们一切要以运行时为准,C++中的block类型有可能在运行时还会被修改。
那么这三种类型的block是如何产生的呢!
但是上面代码中的
block2里面也访问了auto变量age,为啥变成了__NSMallocBlock__类型,这是因为arc自动对block进行了copy,把block2变成了__NSMallocBlock__类型。我们把项目调成MRC,再次运行,发现block2就变成了__NSStackBlock__类型。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,也就是类型由__NSStackBlock__变成__NSMallocBlock__:1.block作为函数返回值时。2.将block赋值给__strong指针时。3.block作为Cocoa API中方法名中含有usingBlock的方法参数时。4.block作为GCD的方法参数时。
第二种情况举例 block2位__NSMallocBlock__类型
int age = 10;
void(^block2)(void) = ^{
NSLog(@"this is a block--%d",age);
};
NSLog(@"%@ %@",[block2 class],[^{
NSLog(@"this is a block--%d",age); //该block为__NSStackBlock__类型
} class]);
第三种情况举例
NSArray *array = @[@"1",@"2"];
[array enumerateObjectsUsingBlock:^(id __Nonnull obj,NSUInteger inx,BOOL * _Nonnull stop){
}];
这样做的目的是为了防止栈上的block销毁后再去访问block报错,所以先将block复制到堆上。
对其他类型的block进行操作的结果:
- 1、当block内部访问了对象类型的auto变量时,如果block在栈上,将不会对auto变量产生强引用。
- 2、当block中访问了对象类型的auto变量且被拷贝到堆上时,会调用block内部的copy函数,copy函数内部调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修辞符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用或者弱引用
- 3、当block中访问了对象类型的auto变量且从堆上移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量。
五、__block
1.原理
__block用于解决block内部无法修改局部auto变量值的问题。 __block不能修饰全局变量、静态变量。
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"this is a block--%d",age);
};
转成C++后,让我们看下__block做了啥。
//age 转化后的结构体
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; //这个指针最终指向它自己
int __flags;
int __size;
int age; //存储值
};
//block内存结构
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;
}
};
//这里已经有copy和dispose函数指针用来对__Block_byref_age_0 *age进行内存管理,也说了__block已经把变量包装成了一个对象
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};
//创建代表age结构体
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
//把age结构体的地址值传入block结构体
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
//block代码块生成的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // 拿到代表age的结构体
(age->__forwarding->age) = 20;//age->__forwarding拿到还是代表age的结构体,再获取age去修改。这里为啥不是age->age = 20呢?我们稍后再说
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fq_cwz6482s7clc_d15gl6nbm340000gn_T_main_972bd2_mi_0,(age->__forwarding->age));//访问也是通过
}
总结:编译器会将__block变量包装成一个对象,也可以说是一个结构体,这个结构体里面存储这个变量的值。
2.内存管理
-
当block在栈上时,不会对__block变量产生强引用。
-
当block被拷贝到堆时,__block变量也会被复制到堆
1.会调用block内部的copy函数
2.copy函数内部会调用_Block_object_assign函数
3._Block_object_assign函数会对堆上的__block变量形成强引用,
这里跟捕获nsobject对象不同。
-当block从堆中移除后
1.会调用block内部的dispose函数
2.disponse函数内部会调用_Block_object_dispose函数
3._Block_object_dispose函数会自动引用的__block变量。
现在看一个问题:
(age->__forwarding->age) = 20;//这里为啥不是age->age = 20呢???
这是为了确保不论访问栈上的block还是堆上的block,最后都是访问的堆上的__block中的age。
六、循环引用
__weak typeof(self) weakSelf = self; // weakSelf(弱引用表) -> self
self.block = ^{
// strong-weak-dance
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
- 如果在block中使用self,那么block会持有该self,假如同时self也持有block,就造成了循环引用
- 我们可以在block外面创建一个弱引用weakSelf来替代self,这样block保存的weakSelf不会对self的引用计数造成影响,这样就不会造成循环引用了
- 但是block内部使用weakself可能会被延迟使用,有可能当bock使用weakself时候self已经被释放了,这个时候weakself也变成nil了
- 为了保证block内部使用weakself正常,这个时候就需要在block内部创建一个strongself指向weakself,这样在block内部就可以通过strongself拿到self的值,而且strongself是block内部的临时变量,block会释放他,并且strongself和block的生命周期是一样的,所以可以保证block内部使用self正常。