底层二: block
转换解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
普通转换
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
block 的底层实现
block是封装了函数调用以及函数调用参数的OC对象
后面复习,可以自己先动手转换一个CPP代码,再对照下面的注释学习
Block 的定义以及实现代码
int age = 20;
void (^block)(void) = ^{
NSLog(@"this is a block %d",age);
};
age = 10;
block();
直接转换代码(去掉强制转换部分)
int age = 20;
// 1. 通过构造函数创建
// 2.1 这里可以看出,age 之前只是作为值传递到block中,后面修改age的值,不会对block产生任何影响
void(*block)(void) =
&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age
);
// 2.2 age在这里修改,不会对block内部的age产生作用
age = 10;
// 3. 这里直接使用__main_block_impl_0 类型的 block 去访问 __block_impl对象中的FuncPtr ,可以从内存的层面去考虑
block->FuncPtr(block);
转换后的C++代码,分成三个部分
struct __main_block_impl_0
struct __block_impl
struct __main_block_desc_0
下面进行分别介绍:
struct __main_block_impl_0 : Block的具体实现,包含struct __block_impl 和 struct __main_block_desc_0
struct __main_block_impl_0 {
// 注意这里不是指针,可以直接把结构体变量拿过来.
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
struct __block_impl: 这里是Block的具体实现细节
struct __block_impl {
void *isa;
int Flags;
int Reserved;
// block 的函数实现
void *FuncPtr;
};
struct __main_block_desc_0: Block 的描述
struct __main_block_desc_0 {
size_t reserved;
// block的尺寸
size_t Block_size;
};
重写结构体
// 这里可以看出block的本质,是 __main_block_impl_0 结构体指针
struct __main_block_impl_0 *impBlock = (__bridge struct __main_block_impl_0 *)block;
通过断点可以获取到block的一些内部信息,通过断点在Block实现部分,反编译到汇编,看最上面的一行调用栈,可以得出结论,__block_impl 内的void *FuncPtr 是函数的实现
block底层实现图解(如果block捕获参数,会把对应的参数放到指定的位置上)
这里的参数指的是捕获的变量
block 的变量捕获( capture )
auto: 自动变量,离开作用域自动销毁(无法定义全局变量)
static:修饰局部变量的时候,产生静态局部变量
静态局部变量有以下特点:
1. 该变量在全局数据区分配内存;
2. 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
3. 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
4. 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
应用
int main()
{
fn();
fn();
fn();
}
void fn()
{
static int n = 10;
printf("%d", n);
n++;
}
| 变量类型 | 捕获到block内部 | 访问方式 |
|---|---|---|
局部变量 auto类型 |
True |
值传递 |
局部变量 static类型 |
True |
指针传递 |
| 全局变量 | false |
直接访问 |
局部变量传递原因: auto变量出了作用域就进行了释放,这个时候block内要访问这个变量,就必须进行值的捕获,否则会造成坏内存访问。而static始终驻留在全局数据区,直到程序运行结束。
全局变量不会捕获原因: 静态局部变量虽然不会销毁,但是静态局部变量作用域会进行销毁,导致外部无法访问,所以捕获指向静态局部变量的指针。而全局变量作用域也不会进行销毁,可以直接访问,不需要捕获。
int age = 20;
static int height = 30;
void(^block)(void) = ^{
NSLog(@"age = %d, height = %d",age, height);
};
age = 10;
height = 10;
block();
转化后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
// 看出赋值过程 age(_age), height(_height),因为是指针传递,所以Block内部捕获到的值会跟随改变
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height)
...
};
block 的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- NSGlobalBlock ( _NSConcreteGlobalBlock )
- NSStackBlock ( _NSConcreteStackBlock )
- NSMallocBlock ( _NSConcreteMallocBlock )
在ARC环境下验证
int age = 20;
void(^block)(void) = ^{
NSLog(@"one = %d",age);
};
void(^block1)(void) = ^{
NSLog(@"two");
};
NSLog(@"\n %@ \n %@ \n %@ \n", [block class],[block1 class], [^{
NSLog(@"one = %d",age);
} class]);
打印结果
__NSMallocBlock__
__NSGlobalBlock__
__NSStackBlock__
int height = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
NSLog(@"数据段 - %p", &height);
NSLog(@"栈段 - %p", &age);
NSLog(@"堆段 - %p", [[NSObject alloc] init]);
// 猜测某个类型在哪个字段?
NSLog(@"%p", [JXPerson class]);
}
return 0;
}
打印结果如下
数据段 - 0x1000011d0
栈段 - 0x7ffeefbff54c
堆段 - 0x101807ae0
// 可以推测出类对象保存在数据区域
---- 0x1000011a8
程序区域一般放代码,包括main函数这些
数据区域一般放全局变量
堆段放alloc出来的对象: 动态分配内存,要程序猿自己管理内存(申请、释放)
栈空间一般放局部变量,自己分配、释放
block 类型的判断标准
| block类型 | 环境 |
|---|---|
__NSGlobalBlock__ |
没有访问auto变量 |
__NSStackBlock__ |
访问了auto变量 |
__NSMallocBlock__ |
__NSStackBlock__调用了copy |
在 MRC 环境下验证
void(^block)(void) = ^{
NSLog(@"one");
};
int age = 10;
void(^block2)(void) = ^{
NSLog(@"%d",age);
};
Block block3 = [block2 copy];
NSLog(@"\n%@ \n%@ \n%@", [block class], [block2 class], [block3 class]);
block的copy
每一种类型的block调用copy后的结果如下所示
| Block的类 | 副本源的配置存储域 | 复制效果 |
|---|---|---|
| _NSConcreteStackBlock | 栈 | 从栈复制到堆 |
| _NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
| _NSConcreteMallocBlock | 堆 | 引用计数增加 |
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况(自动调用一次copy)
block作为函数返回值时 (masory)- 将
block赋值给__strong指针时block作为Cocoa API中方法名含有usingBlock的方法参数时(数组遍历)block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法(在ARC情况下会自动复制strong和copy两种类型引用效果相同),
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
对象类型的auto变量
// 将person对象捕获
JXPerson *person = [[JXPerson alloc] init];
person.age = 10;
JXBlock block= ^{
NSLog(@"------%d", person.age);
};
转换为CPP代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 捕获的person变量是通过auto修饰的,如果通过static修饰,会变成 JXPerson ** person
JXPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JXPerson *_person, int flags=0) : person(_person) {
... ...
}
};
这里将带有weak的变量,转换成CPP代码
__weak JXPerson *weakPerson = person;
block= ^{
NSLog(@"------%d", weakPerson.age);
} ;
转换代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 注意这里会有一个__weak 修饰,同外面 对象类型的auto变量的修饰符 相同
JXPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JXPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
... ...
}
};
将带有对象类型的auto变量的block,转换成CPP代码,可以看出结构体__main_block_desc_0 会有所改变
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 结构体多出两个函数指针,对应两个函数如下
如果Block被拷贝到堆上,会调用内部的__main_block_copy_0 函数(以下简称copy函数),copy函数会调用 _Block_object_assign 函数, 会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
//调用 _Block_object_assign 函数
_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(类似于release)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
当block内部访问了对象类型的auto变量时
- 无论
MRC或者ARC,如果block是在栈上,将不会对auto变量产生强引用 <栈空间的Block没法保住对象类型的auto变量的命> (在MRC环境通过栈block来进行验证,或者在ARC环境通过没有强引用指针指向的Block来验证)
//MRC 环境
JXBlock block;
{
JXPerson *person = [[JXPerson alloc] init];
person.age = 10;
block= ^{
NSLog(@"------%d", person.age);
};
// 如果调用了copy,将block转换为__NSMallocBlock__,将保住person的命
// block= [^{
// NSLog(@"------%d", person.age);
// } copy];
[person release];
}
NSLog(@"-------");
在 NSLog(@"-------"); 处打断点,重写JXPerson的dealloc方法,打印结果如下:
// 重写dealloc的打印信息
deallOC - JXPerson 释放
| 函数 | 调用时机 |
|---|---|
| copy函数 | 栈上的Block复制到堆时 |
| dispose函数 | 堆上的Block被废弃时 |
总结:
如果
Block是在栈空间,不会对内部的对象类型的auto变量产生强引用. 一旦Block在堆空间,会根据变量自身的修饰符(强引用或者弱引用)来进行相应的引用操作。
__block
block 无法修改局部变量,转换成CPP代码可以看到本质,就是两者并不在同一个函数内,如果想要修改可以使用静态局部变量(通过指针引用,可修改) 或者使用__block
1.__block可以用于解决block内部无法修改auto变量值的问题
2.__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__block int age = 10;
void(^myBlock)(void) = ^{
NSLog(@"%d",age);
};
转换: 这里是block所转换的结构体,可以看出对应的age是一个指向__Block_byref_age_0 结构体的指针.从构造函数的赋值 age(_age->__forwarding) 可以看出最后是通过结构体的fowarding指针进行的
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) {
... ...
}
}
我们找到 __Block_byref_age_0 的定义后发现,真实age的值,被放到结构体中
// __ block 会单独生成一个结构体
struct __Block_byref_age_0 {
// 因为包含isa,所以__block 相当于一个对象
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
然后找到函数中 __block int age = 10; 转换的代码.我们发现了如下的一个结构体赋值过程,10被赋值给结构体内的age,而__forwarding指向自身
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10
};
内存管理
__block 修改普通变量
我们可以看到刚刚在block中引用带有__block的代码,转换后的代码描述部分如下,生成了管理对象的函数copy和dispose,原因是因为__block相当于一个对象,在block内部,需要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*);
}
当block在栈上时,并不会对__block变量产生强引用(可以通过MRC,使用栈空间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)
总结:对象类型的auto变量和__block修饰的变量
相同点
- 当
Block在栈上时,不会对auto对象类型、__block修饰的变量产生强引用- 当
Block拷贝到堆上时,会对auto对象类型、__block修饰的变量调用copy函数。- 如果
Block从堆中移除,会对auto对象类型、__block修饰的变量调用dispose函数。
区别
- 使用
__block修饰的基本数据类型在block中肯定是强引用,基本数据类型没法使用__weak修饰, 但是使用__block修饰的对象如果使用了weak、unsafe_retain的话,会是弱引用,对象使用了strong的话,就是强引用
__forwarding 指针
__block 修饰的对象类型
__block JXPerson *person = [[JXPerson alloc] init];
void(^myBlock)(void) = ^{
NSLog(@"%@",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) {
... ...
}
};
__Block_byref_person_0 是一个单独的结构体, 当__Block_byref_person_0 被拷贝到堆上时,会调用 __Block_byref_id_object_copy 函数,将对应的person调用_Block_object_assign 方法,相反释放的时候,调用dipose函数 (在MRC情况下,不会对person形成强引用,肯定是弱引用)
struct __Block_byref_person_0 {
void *__isa; // 8
__Block_byref_person_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
// 比基本数据类型,对了两个函数指针
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 8
// 这里是对应的对象变量(比基本数据类型多了类型修饰符)
JXPerson *__strong person;
};
通过查看main函数的CPP源码,可以拿到__Block_byref_person_0 对应的两个函数指针对应的函数实现__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// 这里的dst指的是结构体的首地址,+40 正好对应到 JXPerson *__strong person; 的位置
// 所以这里的目的是对person对象,调用 assign 函数
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
在 ARC 情况下
对
__block都是强引用对
对象是按照对象的修饰词weak和strong决定对
__block对象这种类型是怎么修饰的,应该是按照__block结构体对象强引用,结构体对象内部的类对象是按照对象的weak和assign来决定引用类型
在MRC情况下
__Block person = [Person new]
JXBlock myBlock = ^{
nslog(@"%@",person);
}
person 永远都是弱引用
block 循环应用
__unsafe_unretained 对象销毁后不会置空
__weak 对象销毁后置为nil
ARC
1.除了弱引用,还可以使用__block来解决循环引用,但是需要进行释放操作,需要调用block
2.在block内部使用__ strong 修饰 weakSelf 的目的: 避免在使用中weakSelf释放掉,举例子: weakSelf -> age 无法通过编译(解决编译器问题)

浙公网安备 33010602011771号