Block
Block 概念
- Block是带有自动变量的匿名函数。如字面意思,Block没有函数名,另外Block带有插入记号"^",插入记号便于查找到Block,后面跟的一个括号表示块所需要的一个参数列表。和函数一样,可以给块传递参数,并且也具有返回值。
- 不同点在于,块定义在函数或者方法内部,并且能够访问在函数或方法范围内的任何变量。通常情况下,这些变量能够访问但不能改变其值,有一个特殊的块修改器(由块前面由两个下划线字符组成)能够修改块内变量的值。
- Blocks 也被称作 闭包、代码块。展开来讲,Blocks
就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。
🌰
首先写一个简单的Block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"B123");
};
block();
}
return 0;
}
使用命令行将代码转化为c++查看其内部结构
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
C++代码如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;//描述block的信息
// 构造函数(类似于OC的init方法),返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block内代码地址
Desc = desc; // 储存block对象占用的大小
}
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_e9907b_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;
// 定义block变量以及实现
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
对比之下,原来OC下的Block相关代码
void(^block)(void) = ^{
NSLog(@"B123");
};
对应的C++代码就是:
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
通过C++代码发现,block的调用实际上就是__main_block_impl_0这个结构体,结构体实现如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;//描述block的信息
// 构造函数(类似于OC的init方法),返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block内代码地址
Desc = desc; // 储存block对象占用的大小
}
};
__main_block_impl_0结构体内部有一个与结构体同名的__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)函数,这是C++结构体的写法,该函数为结构体的构造函数,相当于OC类中的- (instancetype)init;方法。
__main_block_impl_0函数携带三个参数
1. void *fp
2. struct __main_block_desc_0 *desc
3. int flags=0
它有两个必传的参数,一个是函数指针fp ,一个是结构体指针desc,关于结构体指针所指向的结构体就是上面分析到的__main_block_desc_0
,那么第一个参数函数指针fp到底是什么?
在这个demo的C++实现代码中,fp指向的函数为__main_block_func_0
,而__main_block_func_0
就是block里面执行的那段代码,这里是封装成了这样一个函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_e9907b_mi_0);
}
接下来我们来看第二个参数 struct __main_block_desc_0 *desc
static struct __main_block_desc_0 {
size_t reserved; // 0
size_t Block_size; //Block的内存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
我们在执行
void(block)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 传入这个函数的地址然后赋值给结构体内部变量
最后一个参数为可选的,默认值为0。
由此可得知Block本质是一个结构体,那我们看一下这个结构体内部
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
接下来在对比一下调用代码
OC
block();
C++
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
我们可以看到调用Block是先强制转换为impl,然后调用FuncPtr
🌰🌰
复杂一点的Block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age;
age = 30;
void(^block)(void) = ^(){
NSLog(@"%d",age);
};
age = 40
block();
}
return 0;
}
用过Block的人肯定都熟悉,打印结过为30,那我我们看一下底层实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
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; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_440a9c_mi_0,age);
}
可以看出Block内部生成了一个age变量用来存储,而构造block的值会将其传入内部,所以其值不会引起改变
那现在也可以很清楚Block底层的内部结构如下
capture
概念
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
Capture 变量,可以 Capture Block 定义所在的作用域内的变量,类似于保存上下文。之后,Block 在别处被调用时,就好像 Block 还在原本上下文一样,可以正常执行。这些都是 OC 自动做的事情。而我们在定义 Block 的时候,当前作用域内的变量,都可以在 Block 中使用。不需要顾及当 Block 在别处被调用
🧀️
我们定义三种类型的变量
- 全局变量
- 局部变量
- 静态变量
int globalint ;
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int stakeint = 10;
static int staticint = 20;
void(^block)(void) = ^(){
NSLog(@"%d%d%d",globalint,stakeint,staticint);
};
// void(^block)(int,int) = ^(int a,int b){
// NSLog(@"%d",a + b);
// };
struct __main_block_impl_0 * blockstruct = (__bridge struct __main_block_impl_0*)block;
block();
}
C++底层
//Block结构体声明
int globalint ;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int stakeint;
int *staticint;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _stakeint, int *_staticint, int flags=0) : stakeint(_stakeint), staticint(_staticint) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 调用函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int stakeint = __cself->stakeint; // bound by copy
int *staticint = __cself->staticint; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_faa523_mi_0,globalint,stakeint,(*staticint));
}
总结一下 原理图
当我们判断是否Block会捕获对象是我们需要判断是否为全局变量,若是则不捕获,反之则捕获
Block的类型
看了底层实现 肯定会发现 其中有一句 如下
impl.isa = &_NSConcreteStackBlock;
这其实就是在说明Block的对象
我们先声明一个这个Block ,然后打印其父类至根类
void(^blockTest)(void) = ^(){
NSLog(@"Test");
};
NSLog(@"%@",[blockTest class]);
//NSLog(@"%@",[[blockTest class] superclass]);
NSLog(@"%@",[blockTest superclass]);
NSLog(@"%@",[[blockTest superclass]superclass]);
NSLog(@"%@",[[[blockTest superclass]superclass]superclass]);
打印结果如下
我们可以看到最后的根类为NSObject,所以也可以说明Block是一个OC对象
类型详解
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- NSGlobalBlock ( _NSConcreteGlobalBlock )
- NSStackBlock ( _NSConcreteStackBlock )
- NSMallocBlock ( _NSConcreteMallocBlock )
Block在内存中的布局
Block的类型分配
🍪
oid (^Block)(void);
void test3() {
int number = 10;
Block = ^{
NSLog(@"%d", number);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test3();
Block();
}
return 0;
}
首先说明 在ARC和MRC 会是不同的结果 ,了解过内存管理应该知道ARC是编译器特性,就是编译器帮助我们做了很多事情,简化了我们的代码量先附上MRC的打印结果
其次我们可以看到Block的类型为
而这里之所以会打印为错值,道路也很简单就是因为你之前Block结构体的内存空间在Stack 上,而test 方法使用后便会被回收,所以打印错值,那想要解决这个问题也很简单,就是我们让Block存储在堆上即可,那么根据之前的流程图,只要我们进行一次copy
操作即可
首先我们看Block的类型为
其次打印值也为10
🍪🍪
那我们在看第二个测试结果
void test3() {
int number = 10;
Block = [^{
NSLog(@"%d", number);
} copy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^BlockTest)(void)= [^{
NSLog(@"123");
} copy];
NSLog(@"%@", [BlockTest class]);
}
test3();
[Block copy];
NSLog(@"%@", [Block class]);
[Block release];
return 0;
}
// BlockTest 为 __NSGlobalBlock__
// 打印结果 __NSGlobalBlock__
// __NSMallocBlock__
ARC下的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
总结一下
每一种类型的block调用copy后的结果如下所示
对象类型的auto变量
🍐
一个例子引出这个
{
Person * person = [[Person alloc] init];
person.age =10;
}
NSLog(@"1111111");
Block block;
{
Person * person = [[Person alloc] init];
person.age =10;
block = ^{
NSLog(@"%d",person.age);
};
}
NSLog(@"1111111");
打印结果
// 第一个在打印1111111之前对象会释放
//第二个在打印1111111之前对象不会释放
那我们继续转成C++代码查看一下底层实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_00319d_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
我们可以看到这个Block内部会生成一个person对象,而 Person *person = __cself->person
,就是让Block内部的person指向 外面实例对象的person,由于外面的实例对象此时被block持有所以自然不会被释放,当Block被释放的时候,person就会被释放
🍐🍐
Block block;
{
Person * person = [[Person alloc] init];
person.age =10;
__weak Person * weakPerson = person;
block = ^{
NSLog(@"%d",weakPerson.age);
};
}
NSLog(@"1111111");
// 在打印1111111之前对象会释放
C++底层实现
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__weak weakPerson = __cself->weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_9af25a_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
}
//我们可以发现底层person的修饰为weak
//同时我们发现多出了两个函数
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};
总结一下
当block内部访问了对象类型的auto变量时 如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用如果block从堆上移除 会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
说到这里我们可以看一下完整的Block的内部结构
__block
作用
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
🧊
typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 5 ;
Block block = ^{
age = 10;
NSLog(@"%d",age);
};
block();
}
那我们继续转换成C++代码
typedef void(*Block)(void);
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int 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
(age- g->age) = 10;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_526302_mi_0,(age->__forwarding->age));
}
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};
// __block int age = 5 ;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
5 };
Block block = ((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 *)block)->FuncPtr)((__block_impl *)block);
我们可以发现 编译器会将__block变量包装成一个对象,从而可以用于解决block内部无法修改auto变量值的问题
⚠️ 那么这里我们如果打印age是那个值呢?
__block int age = 5 ;
Block block = ^{
age = 10;
NSLog(@"%d",age);
};
block();
struct __main_block_impl_0 * blockLook = (__bridge struct __main_block_impl_0*)block;
NSLog(@"%p",&age);
在这里我们可以看到是
(__Block_byref_age_0 *) age = 0x00000001005526b0
但是我们的打印结果为 相差了 24 ,我们回过头看这个结构体答案就应该显而易见了
就是里面的age,所以我们修改的时候是封装后的值
__block的内存管理
当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)
如下图
当block从堆中移除时 会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)
__block修饰对象
🍉
先写一个简单的例子
//OC
__block Person * person = [[Person alloc] init];
MyBlock block = ^{
NSLog(@"%p", person);
};
C++
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *person;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_fd7a72_mi_0, (person->__forwarding->person));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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*);
}
我们可以了解到其内存结构如下图
那么是指针是强还是弱,我们可以用__weak修饰一下
Person * person = [[Person alloc] init];
__block __weak Person * weakperson = person;
MyBlock block = ^{
NSLog(@"%p", weakperson);
};
struct __Block_byref_weakperson_0 {
void *__isa;
__Block_byref_weakperson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakperson;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakperson_0 *weakperson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakperson_0 *_weakperson, int flags=0) : weakperson(_weakperson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
得知__block的对象一直为强指针,但是内部指针是强还是弱取决于你的修饰,我们在看一下 __Block_byref_weakperson_
的内部结构
struct __Block_byref_weakperson_0 {
void *__isa;
__Block_byref_weakperson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakperson;
};
发现内部也有
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
这其实也对其对象进行了引用技术管理
概括一下
对象类型的auto变量、__block变量
相同点
当block在栈上时,对它们都不会产生强引用
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
不同点
__Block都会对其对象进行持有
__block的__forwarding指针
刚才有说到对其赋值的时候是这样的
(age->__forwarding->age) = 10;
之所以这样请看下图,从而我们不论在栈还是队列都可以访问到__block修饰的变量
_forwarding指向了自身。 在block构造的时候我们用到了a.__forwarding, 在使用的时候(__BlockTest__test_block_func_0)也是通过(a->__forwarding->a)这样的方式找到最终修改过的数值‘8’。
能做到这样的原因上面已经说过了,这里复述一下:
栈上的__block变量复制到堆上时,会将成员变量__forwarding的值替换为复制到堆上的__block变量用结构体实例的地址。所以“不管__block变量配置在栈上还是堆上,都能够正确的访问该变量”,这也是成员变量__forwarding存在的理由。参照上面的图
Block的循环引用
♻️
Block 实质
- block本质上也是一个对象,而这个对象是一个结构体,其内部含有一个isa指针指向自己的类。
- block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象。
参考的相关资料
Objecitive-C高级编程