OC底层原理30:Block底层原理

张建 lol

Block类型

block 主要有三种类型: 全局区、堆区、栈区

  • __NSGlobalBlock__ :全局block,存储在 全局区
1
2
3
void(^block)(void) = ^{
NSLog(@"ZJ");
};

此时的block无参数,也无返回值,属于 全局block ,如下图所示:

  • __NSMallocBlock__ :堆区block,因为block即是 函数 ,也是 对象
1
2
3
4
5
6
7
- (void)mallocBlock{
int a = 10;
void(^ block)(void) = ^{
NSLog(@"ZJ - %d",a);
};
NSLog(@"%@",block);
}

此时的block会访问外部变量,即 底层拷贝a ,所以是 堆区block

1
2021-08-02 11:11:56.986272+0800 001---Block深入浅出[1126:696422] <__NSMallocBlock__: 0x2800e3c00>
  • __NSStackBlock__ :block本身默认是 栈block
1
2
3
4
5
6
7
8
9
10
11
12
// 栈block
- (void)stackBlock{
__block int a = 10;
void(^block)(void) = ^{
a = 20;
NSLog(@"ZJ - %d",a);
};
NSLog(@"%@",^{
a = 20;
NSLog(@"ZJ - %d",a);
});
}

【小知识点】:当block内部需要修改或访问外部变量a时,外部变量需要额外用 __block 修饰,否则修改不了

我们来看一下结果:

1
2021-08-02 11:15:47.243001+0800 001---Block深入浅出[1131:698266] <__NSMallocBlock__: 0x28037a700>

为什么?居然还是 __NSMallocBlock__ ,堆区?
答:因为在 ARC 环境下,编译器自动帮我们加了 copy 操作。在 局部变量a 在没有处理(即没有 拷贝 之前)是 栈区block,处理之后(即 拷贝 之后)是 堆区block,目前的栈区block越来越少了。

当我们关掉ARC如下:

再来打印一下结果:

【总结】

  • block直接存储在 全局区

  • 如果 block访问外界变量 ,并进行block相应拷贝,即 copy

    • ARC 环境下,存储在堆区
    • 非ARC 环境下,存储在栈区

Block变量捕获

为了保证Block内部能够正常访问外部变量,block有个自动捕获外部变量的机制

  1. auto 类型局部变量:局部变量截获 是值截获

例子:

1
2
3
4
5
6
7
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
// num = 4; // 编译器报错
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));

查看打印结果:

1
2
这里的输出是6而不是2,原因就是对局部变量num的截获是值截获。
同样,在block里如果修改变量num,也是无效的,甚至编译器会报错。
  1. static 类型局部变量:局部静态变量截获是 指针截获

例子:

1
2
3
4
5
6
7
// 静态变量-指针捕获
static int a = 10;
NSInteger(^Sblock)(NSInteger) = ^NSInteger(NSInteger n){
return n*a;
};
a = 1;
NSLog(@"%zd",Sblock(2));

打印结果:

1
2020-08-02 00:49:30.467652+0800 OC-Block初探[15224:868821] 2

结论:

1
静态局部变量截获的是指针,在外部修改为1,对block有影响
  1. __block修饰的变量: __block 变量截获是 指针截获,并且生成了一个新的结构体对象

例子:

1
2
3
4
5
6
7
// __block修饰变量-指针捕获
__block NSInteger n = 3;
void(^Bblock)(void) = ^{
NSLog(@"%zd",n); // __block修饰变量
};
n = 4;
Bblock();

打印结果:

1
2020-08-02 01:01:44.760288+0800 OC-Block初探[15345:878899] 4

结论:

1
__block修饰的变量也是以指针形式截获的

解释:__block 是将外部变量包装成了一个对象并将 n 存在这个对象中,实际上block外面的 n 的地址也是指向这个对象中存储的 n 的,而block底层是有一个指针指向这个对象的,所以当外部更改n时,block里面通过指针找到这个对象进而找到n,然后获取到n的值,所以n发生了变化。

  1. 全局变量、静态全局变量:不截获,直接取值

例子:

1
2
3
4
5
6
7
8
9
10
11
12
NSInteger num1 = 3; // 全局变量
static NSInteger num2 = 30; // 静态全局变量
- (void)blockTest{
void(^block)(void) = ^{
NSLog(@"%zd",num1); // 全局变量
NSLog(@"%zd",num2); // 全局静态变量
};
num1 = 4;
num2 = 40;

block();
}

打印结果:

1
2
2020-08-02 01:01:44.760418+0800 OC-Block初探[15345:878899] 4
2020-08-02 01:01:44.760514+0800 OC-Block初探[15345:878899] 40

结论:

1
全局变量、静态全局变量是不截获,直接取值
  1. 对象:对象类型的也是一样的,值截取

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)blockTest1{
// 局部对象
NSMutableArray * arr = [NSMutableArray arrayWithObjects:@1,@2, nil];
void(^block)(void) = ^{
NSLog(@"%@",arr);
[arr addObject:@(4)];
NSLog(@"%@",arr);
};
[arr addObject:@3];
arr = nil;
block();
}

查看打印结果:

1
2
3
4
5
6
7
8
9
10
11
2020-08-11 10:41:59.416461+0800 OC-Block变量截获的原理[31602:1829372] (
1,
2,
3
)
2020-08-11 10:41:59.416882+0800 OC-Block变量截获的原理[31602:1829372] (
1,
2,
3,
4
)

结论:
局部对象变量也是一样,截获的是值,而不是指针,在外部将其置为nil,对block没有影响,而该对象调用方法会影响

Block循环引用

  • 正常释放:是指A持有B的引用,当A调用dealloc方法,给B发送release信号,B收到release信号,如果此时B的retainCount(即引用计数)为0时,则调用B的dealloc方法

  • 循坏引用:A、B互相持有,所有导致A无法调用dealloc方法给B发送release信号,而B也无法接收到release信号,所以A、B此时都无法释放

关系图如下:

Block循环引用解决

请问下面两段代码是否有循环引用?

1
2
3
4
5
6
7
8
9
10
// 代码一
self.name = @"ZJ";
self.block = ^{
NSLog(@"%@",self.name);
};

// 代码二
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
}];
  • 【代码一】:发生了 循环引用,因为在 block 内部使用了 外部变量name,导致 block持有了self,而 self 原本是持有 block 的,所以导致 self和block的相互持有,即 self -> block -> self -> name

  • 【代码二】:没发生 循环引用 ,虽然使用了 外部变量name,但是 self并没有持有animationblock,仅仅只有 block 持有 self ,不构成互相持有,即 block -> self -> name

解决循环引用常见的几种方式

  • 【方式一】: weak-strong-dance 强弱共舞

  • 【方式二】: __block 修饰对象(需要注意的是在block内部需要 置空 对象,而且 block必须调用

  • 【方式三】: 传递 对象self 作为block的参数,提供给block内部使用

  • 【方式四】: 使用 NSProxy

  1. 【方式一】:weak-strong-dance
  • 如果block内部并未嵌套block,直接使用 __weak 修饰 self 即可:
1
2
3
4
5
6
7
8
typedef void(^ZJBlock)(void);
...
@property (nonatomic, copy) ZJBlock block;
...
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf.name);
};

此时的 weakSelfself 指向同一片 内存空间,且使用 __weak不会导致self的引用计数发生变化,可以通过打印 weakSelfself 的指针地址和引用计数来验证,如下所示:

运行下面的代码:

1
2
3
4
5
6
7
8
9
10
// block
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(self)));
self.block = ^{
NSLog(@"%@",weakSelf.name);
};
NSLog(@"%p - %p",weakSelf,self);
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self.block)));
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));

得到的结果如下:

  • 如果 block 内部嵌套 block,需要同时使用 __weak__strong
1
2
3
4
5
6
7
8
9
10
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);

// 内部执行完 strongSelf 就立即释放
});
};
self.block();

其中 strongSelf 是一个 临时变量 ,在block的作用域内,即内部 block执行完 就释放 strongSelf

这种方式属于 打破self对block的强引用,依赖于 中介者模式,属于自动置为 nil,即 自动释放

  1. 【方式二】:__block修饰变量

这种方式同样依赖于 中介者模式,属于 手动释放,是通过 __block 修饰对象,主要是因为 __block 修饰的对象时可以改变的

1
2
3
4
5
6
7
8
__block ViewController * vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();

需要注意的是这里的 block必须调用,如果不调用block,vc就不会被置空,那么依旧是循环引用,selfblock 都不会被释放

  1. 【方式三】:对象self作为参数

主要是将 对象self作为参数 ,提供给block内部使用,不会有引用计数问题:

1
2
3
4
5
6
self.block = ^(BlockViewController * vc) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
  1. 【方式四】:NSProxy虚拟类
  • oc 是只能 单继承 的语言,但是它是 基于运行时的机制,所以通过 NSProxy 来实现 伪多继承 ,填补了多继承的空白

  • NSProxyNSObject 是同级的一个类,也可以说是一个虚拟类,只是实现了 NSObject 的协议

  • NSProxy 其实是一个 消息重定向封装的一个抽象类,类似于一个 代理人,中间件,可以通过继承它,并重写两个方法来实现消息转发到另一个实例

1
2
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");

使用场景

NSProxy 的使用场景主要有两种:

  • 实现 多继承 功能
  • 解决了 NSTimer & CADisplayLink 创建时 对self强引用 的问题,参考 YYKitYYWeakProxy

循环引用解决原来

主要是通过自定义 NSProxy 类的对象来代替 self,并使用方法实现消息转发

下面是 NSProxy 子类的实现以及使用的场景:

【场景一】:多继承

  • 自定义一个 NSProxy 的子类 ZJProxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@interface ZJProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end

@interface ZJProxy ()
@property(nonatomic, weak, readonly) NSObject *objc;
@end
@implementation ZJProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}

+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}

// 1.获取target类中的sel方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.objc methodSignatureForSelector:sel];
}
// 2. 类似于方法重定向
- (void)forwardInvocation:(NSInvocation *)invocation {
// 判断objc是否实现了该方法
if ([self.objc respondsToSelector:invocation.selector]) {
// 让objc调用该方法
[invocation invokeWithTarget:self.objc];
}else {
// 找不到该方法
[invocation doesNotRecognizeSelector:invocation.selector];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}

@end
  • 自定义 CatDog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//********Cat类********
@interface Cat : NSObject
@end

@implementation Cat
- (void)eat{
NSLog(@"猫吃鱼");
}
@end

//********Dog类********
@interface Dog : NSObject
@end

@implementation Dog
- (void)shut{
NSLog(@"狗叫");
}
@end
  • 通过 ZJProxy 实现 多继承
1
2
3
4
5
6
7
8
9
Dog * dog = [[Dog alloc] init];
Cat * cat = [[Cat alloc] init];
ZJProxy *proxy = [ZJProxy alloc];

[proxy transformObjc:cat];
[proxy performSelector:@selector(eat)];

[proxy transformObjc:dog];
[proxy performSelector:@selector(shut)];

【场景二】:通过 ZJProxy 解决 NSTimer计时器中self强引用 问题

1
2
self.timer = [NSTimer timerWithTimeInterval:1 target:[ZJProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

别忘了在 dealloc 中调用 [self.timer invalidate]

【场景三】:通过 ZJProxy 解决 CADisplayLink计时器中self强引用 问题

1
2
self.link = [CADisplayLink displayLinkWithTarget:[ZJProxy proxyWithObjc:self] selector:@selector(linkMethod:)];
[self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

同样,别忘了在 dealloc 中调用 [self.link invalidate]

总结

循环引用的解决方式,从根本上来说就两种,以 self -> block -> self 为例:

  • 打破 selfblock 的强引用,可以block属性修饰符使用 weak,但是这样会导致blcok还没有创建完就释放了,所以从这里打破强引用行不通。

  • 打破 blockself 的强引用,主要就是self的作用域和block的作用域的 通讯,通讯有 代理、传值、通知、传参 等几种方式,用于解决循环引用。

    • weak-strong-dance
    • __block(block内对象置空,且调用block)
    • 将对象 self 作为block的参数
    • 通过 NSProxy 子类代替 self

面试题

  1. __Block__weak 的区别
  • __block不管是 ARC 还是 MRC 模式下都可以使用,可以修饰对象,还可以 修饰基本数据类型,不能修饰基本数据类型(int)。

  • __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型(int)。

  • __block对象可以在block中被重新赋值,__weak不可以

  1. block和delegate区别?
  • delegate运⾏成本低,block运⾏成本⾼。
  • delegate是weak弱引⽤;block是copy修饰。
  • block直接访问上下⽂,块和块实现在同⼀个地⽅,代码组织更加连贯。delegate声明和⽅法分离开来,代码的连贯性不是很好。
  1. block 值截取 和 指针截取的区别?
  • 值截取:深拷贝,只拷贝值,拷贝的值不可修改,指向不同的内存空间

  • 指针截取:浅拷贝,拷贝的值可以修改,指向同一片内存空间

  1. 为什么当我们在使用block时外面是weak,声明一个weakSelf,还要在block内部使用strong再持有一下?

block外界声明weak是为了实现block对对象的 弱持有,而里面的作用是为了保证在进到block时不会发生释放。

  1. 为什么 __block 能够修改外部变量?

__block 指针拷贝,实际上 __block 是把外部变量的 指针copy进堆,通过指针可以找到 内存地址 进而修改变量值,所以能进行 a++

  1. __block修饰变量和对象的区别?

__block修饰的变量在block结构体中一直都是 强引用,而其他类型的是由传入的对象指针类型决定。

  1. Block用copy修饰还是strong修饰?

block用copy和strong修饰都可以。

  1. Block变量截获?

block有自动补获外部变量的能力。

  • 局部变量截获:会被block捕获到内部,捕获了变量的值,是 值传递
  • 局部静态变量、__block修饰的变量:会被block捕获到内部,捕获了变量的 指针地址,可以修改值
  • 全局变量,静态全局变量截获:没有捕获,会直接访问。
  1. 为什么auto变量是值传递?static变量是指针传递?
  • 因为 auto 类型的局部变量 出了自己的作用域就被销毁了,这个变量就不存在了,它原来所占的内存就变成了垃圾内存了,不可以再访问,所以针对这种 变量 就需要在 创建block的时候马上保存到block内部,否则在 运行block的时 候这个 变量就可能没了,所以在block创建之后再怎么改变这个变量的值,运行block的时候依然是之前的值 。

  • static 局部变量虽然 出了作用域也不能访问,但它的 内存是一直存在 的,不会销毁,所以block只需要在运行的时候能访问到它就可以,所以针对这种变量block采用的是指针传递,block内部只要 保存 这个 变量内存地址 就可以保证在block运行的时候访问到这个变量,而正因为是指针传递,多以block在运行的时候总能够访问到这个变量最新的值。

  • 看到这里,我们也很容易明白为什么全局变量不用捕获,因为 全局变量既不会被销毁,也可以随处访问,所以block根本 不用去捕获它 也可能随时随地访问到它的值。

  1. 以下代码是否可以正确执行?

1
2
3
4
5
6
7
NSMutableArray * array = [NSMutableArray array];
Block block = ^{
[array addObject: @“5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();

可以正确执行,因为在block块中仅仅是使用了 array的内存地址,往内存地址中添加内容,并没有修改 array 的内存地址,因此 array 不需要使用 __block 修饰也可以正确编译

Block 底层本质

主要是通过 clang、断点调试 等方式分析Block底层

本质

  • 定义 block.c 文件
1
2
3
4
5
6
7
8
#include "stdio.h"

int main(){
void(^block)(void) = ^{
printf("ZJ");
};
return 0;
}
  • block.c 文件目录,通过 xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c,将 block.c 编译成 block.app,其中block在底层被编译成了一下的形式:
1
2
3
4
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}

简化后:

1
2
3
4
5
// 构造函数
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

// block调用执行
block->FuncPtr(block);

相当于 block 等于 __main_block_impl_0,是一个 函数

  • 查看__main_block_impl_0,是一个 结构体,同时可以说明 block 是一个__main_block_impl_0 类型的 对象,这也是为什么 block 能够 %@ 打印的原因
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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_impl的结构体类型
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

【总结】block本质对象、函数、结构体,由于block函数没有名称,也被称为 匿名函数

block 通过 clang 编译后的源码间的关系如下图所示:以 block 修饰的变量为例:

1、block为什么需要调用?

在底层block的类型 __main_block_imp_0 结构体,通过其同名构造函数创建,第一个传入的block内部实现代码块,即 __main_block_func_0,用 fp 表示,然后赋值给 impl 属性,然后在 main 中进行了调用,这也是block为什么需要调用的原因。如果不调用,block内部实现的代码块将无法执行,可以总结以下两点:

  • 函数声明:即block内部实现声明了一个函数 __main_block_func_0

  • 执行具体的函数实现:通过调用block的FuncPstr指针,调用block

2、block是如何获取外界变量的

  • 定义一个变量,并在block中调用
1
2
3
4
5
6
7
int main(){
int a = 10;
void(^block)(void) = ^{
printf("ZJ - %ld",a);
};
return 0;
}
  • 底层编译后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 编译时就自动生成了相应的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock; // block的isa默认是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp; // 函数指针赋值
Desc = desc;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷贝,即 a = 10,此时的a于传入的__cself的a并不是同一个
printf("ZJ - %ld",a);
}

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 a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
return 0;
}

__main_block_func_0 中的 a值拷贝,如果此时在block内部实现中做 a++ 操作,是有问题的,会造成编译器的代码歧义,即此时的 a 是只读的

总结: block捕获外界变量时,在 内部会自动生成同一个属性来保存

__block的原理

  • a 加一个 __block,然后在block中对 a 进行 ++ 操作
1
2
3
4
5
6
7
8
int main(){
__block int a = 10;
void(^block)(void) = ^{
a ++;
printf("ZJ - %ld",a);
};
return 0;
}
  • 底层编译如下:

    • main中的 a 是通过外界变量封装的 对象
    • __main_block_impl_0 中,将 对象a 的地址 &a 给构造函数
    • __main_block_func_0 内部对a的处理是 指针拷贝,此时创建的对象a与传入对象的a 指向同一片内存空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// __block修饰的外界变量结构体
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};

// __block的结构体类型
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// block内部实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝,此时对象a 与 __cself对象的a指向同一片地址空间
// 等同于 外界的a++
(a->__forwarding->a) ++;
printf("ZJ - %ld",(a->__forwarding->a));
}

int main(){
// __Block_byref_a_0 是结构体,a 等于 结构体的赋值,即外界变量a 封装成对象
// &a 是外界变量a的地址
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
// _main_block_img_0中的第三个参数&a,是封装对象a的地址
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
return 0;
}

总结:

  • 外界变量 会生成 __block_byref_a_0 结构体
  • 结构体用来 保存原始变量的指针和值
  • 将变量生成的 结构体对象的指针地址,传递给block,然后在block内部就可以对外界变量进行操作了

两种拷贝对比:

  • 值拷贝:-深拷贝,只是拷贝值,且拷贝的值不可以更改,指向不同的内存空间,案列中普通变量a就是值拷贝

  • 指针拷贝:-浅拷贝,生成的对象指向同一片内存空间,案列中经过__block修饰的变量a就是 指针拷贝

block底层的真正类型

分析block源码所在位置

  • 运行下面的代码,并开启 Always Show Disassembly反汇编
1
2
3
4
5
int a = 10;
void(^block)(void) = ^{
NSLog(@"ZJ = %d",a);
};
block();
  • 通过在block处打断点,分析运行时的block

  • objc_retainBlock 符号断点,发现会走到 _Block_copy

  • _Block_copy 符号断点,运行时在 libsystem_blocks.dylib 源码中

可以到苹果开源网站下载最新的 libcloseure-074 源码,通过查看 _Block_copy 的源码实现,发现block底层的真正类型是_Block_layout

Block的真正类型

查看 Block_layout 类型的定义,是一个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Block 结构体
struct Block_layout {
// 指向表明block类型的类
void *isa; // 8字节
// 用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
// 保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved; // 4字节
// 函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke;
// block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
  • isa:指向表明block类型的类

  • flags:标识符,按bit位表示一些block的附加信息,类似于isa中的位域,其中flags 的种类有以下几种,主要重点关注 BLOCK_HAS_COPY_DISPOSEBLOCK_HAS_SIGNATUREBLOCK_HAS_COPY_DISPOSE 决定是否有Block_descriptor_2BLOCK_HAS_SIGNATURE 决定是否有Block_descriptor_3

    • 第1 位 - BLOCK_DEALLOCATING,释放标记,-般常用 BLOCK_NEEDS_FREE位与 操作,一同传入 Flags , 告知该 block 可释放。
    • 低16位 - BLOCK_REFCOUNT_MASK,存储引用计数的值;是一个可选用参数
    • 第24位 - BLOCK_NEEDS_FREE,低16是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的 值;
    • 第25位 - BLOCK_HAS_COPY_DISPOSE,是否拥有拷贝辅助函数(a copy helper function);
    • 第26位 - BLOCK_IS_GC,是否拥有 block 析构函数;
    • 第27位,标志是否有垃圾回收;
    • 第28位 - BLOCK_IS_GLOBAL,标志是否是全局block;
    • 第30位 - BLOCK_HAS_SIGNATURE,与 BLOCK_USE_STRET 相对,判断当前 block 是否拥有一个签名。用于 runtime 时动态调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 注释: flags 标识
// Values for Block_layout->flags to describe block objects
enum {
// 释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
BLOCK_DEALLOCATING = (0x0001), // runtime
// 存储引用引用计数的 值,是一个可选用参数
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
// 低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
// 是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
// 是否拥有block C++析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
// 标志是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
// 标志是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
// 与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
// 是否有签名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
// 使用有拓展,决定block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
  • reserved:保留信息,可以理解预留位置,猜测是用于存储block内部变量信息

  • invoke:是一个函数指针,指向block的执行代码

  • descriptor: block的附加信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。有三类:

    • Block_descriptor_1 是必选的
    • Block_descriptor_2Block_descriptor_3 都是可选的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;//保留信息
uintptr_t size;//block大小
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷贝函数指针
BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;//签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 布局
};

以上关于 descriptor 的可以从其构造函数中体现,其中 Block_descriptor_2Block_descriptor_3 都是通过 Block_descriptor_1 的地址,经过内存平移得到的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;//默认打印
}
#endif

// 注释:Block 的描述 : copy 和 dispose 函数
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//通过内存平移获取
return (struct Block_descriptor_2 *)desc;
}

// 注释: Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}

block内存变化

1、没有外部变量的 block:

  • 打断点运行,走到 objc_retainBlock,block断点处读取寄存器 x0,此时 block全局block,即 __NSGlobalBlock__

2、增加外部变量a的block:

  • 同上,执行到符号断点 objc_retainBlock,得到的 block栈block,即__NSStackBlock

  • 增加 _Block_copy 符号断点并断住,直接在最后的 ret 加断点,读取 x0,发现经过 _Block_copy 之后,变成了 堆block,即 __NSMallocBlock__,主要是因为block地址发生了改变,为堆 block:

调用情况

  • 同样也可以通过断点来验证

  • register read x0 读取x0,为堆block

  • register read x9 读取x9

  • register read x11 ,此时是指向一片内存空间,用于存储 _block_invoke

  • 按住 control + step into,进入 _block_invoke,可以得出是通过内存平移得到的block内部实现

前面提到的 Block_layout 的结构体源码,从源码中可以看出,有个属性 invoke,即block的执行者,是从 isa 的首地址平移 16 字节取到 invoke,然后进行调用执行的

签名

  • 继续操作,读取 x0 寄存器,看内存布局,通过 内存平移 3*8 就可获得 Block_layout 的属性 descriptor,主要是为了查看是否有 Block_descriptor_2Block_descriptor_3,其中3中有block的签名

    • register read x0,读取寄存器x0
    • po 0x00000002828a2160 , 打印block
    • x/8gx 0x00000002828a2160 ,即打印block内存情况

  • x/8gx 0x00000001008a0010 , 查看descriptor的内存情况,其中第三个0x000000010089f395 表示签名

  • 判断是否有 Block_descriptor_2,即flags的 BLOCK_HAS_COPY_DISPOSE(拷贝辅助函数)是否有值

    • p/x 1<<25 ,即1左移25位,其十六进制为 0x2000000
    • p 0x02000000 & 0x00000000c1000002 ,即 BLOCK_HAS_COPY_DISPOSE & flags ,等于0,表示没有 Block_descriptor_2

  • 判断是否有 Block_descriptor_3

    • p/x 1<<30,即1左移30位
    • p 0x40000000 & 0x00000000c1000002 ,即 BLOCK_HAS_SIGNATURE & flags ,有值,说明有 Block_descriptor_3

  • p (char *)0x000000010089f395 – 获取 Block_descriptor_3 中的属性 signature 签名

  • po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] ,即打印签名

其中签名的部分说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//无返回值
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
//encoding = (@),类型是 @?
type encoding (@) '@?'
//@是isObject ,?是isBlock,代表 isBlockObject
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
//所在偏移位置是8字节
memory {offset = 0, size = 8}

block的签名信息类似于方法的签名信息,主要是体现block的返回值,参数以及类型等信息

block三次copy分析

_Block_copy源码分析

进入 _Block_copy 源码,将 block 从栈区拷贝至堆区

  • 如果需要释放,则直接释放

  • 如果是 globalBlock – 不需要copy,直接返回

  • 反之,只有两种情况:栈区block or 堆区block,由于堆区block需要申请空间,前面并没有申请空间的相关代码,所以只能是 栈区block:

    • 通过 malloc 申请内存空间用于接收block

    • 通过 memmove 将block拷贝至新申请的内存中

    • 设置block对象的类型为堆区block,即 result->isa = _NSConcreteMallocBlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 重点提示: 这里是核心重点 block的拷贝操作: 栈Block -> 堆Block
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;

if (!arg) return NULL;

// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;//强转为Block_layout类型对象,防止对外界造成影响
if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要释放
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block,直接返回
return aBlock;
}
else {//为栈block 或者 堆block,由于堆区需要申请内存,所以只可能是栈区
// Its a stack block. Make a copy. 它是一个堆栈块block,拷贝。
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);//申请空间并接收
if (!result) return NULL;
//通过memmove内存拷贝,将 aBlock 拷贝至result
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;//可以直接调起invoke
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed 告知可释放
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;//设置block对象类型为堆区block
return result;
}
}

_Block_ovject_assign分析

想要分析block的三层copy,首先需要知道外部变量的种类有哪些,其中用的最多的是 BLOCK_FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 注释: Block 捕获的外界变量的种类
// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通对象,即没有其他的引用类型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block类型作为变量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//经过__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用变量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};

_Block_object_assign 是在底层编译代码中,外部变量拷贝时调用的方法就是它

  • 进入 _Block_object_assign 源码

    • 如果是普通对象,则交给 系统arc处理,并拷贝 对象指针,即 引用计数+1,所以外界变量不能释放

    • 如果是 block类型 的变量,则通过 _Block_copy 操作,将block从栈区拷贝到堆区

    • 如果是 __block修饰 的变量,调用 _Block_byref_copy 函数 进行内存拷贝以及常规处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static struct Block_byref *_Block_byref_copy(const void *arg) {

//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;

if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
// copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
// 如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
// 等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}

return src->forwarding;
}
  • 进入 _Block_byref_copy 源码

    • 将传入的对象,强转为 Block_byref 结构体类型对象,保存一份

    • 没有将外界变量拷贝到堆,需要申请内存,其进行拷贝

    • 如果已经拷贝过了,则进行处理并返回

    • 其中 copy 和src的 forwarding 指针都指向同一片内存,这也是为什么__block修饰的对象具有修改能力的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static struct Block_byref *_Block_byref_copy(const void *arg) {

//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;

if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}

return src->forwarding;
}

代码调试

  • 定义一个 __block 修饰的 NSString对象
1
2
3
4
5
6
7
8
 __block NSString * zj_name = [NSString stringWithFormat:@"ZJ"];
void (^block1)(void) = ^{ // block_copy
zj_name = @"ZJ";
NSLog(@"ZJ - %@",zj_name);

// block 内存
};
block1();
  • xcrun编译结果如下:

    • 编译后的 zj_name 比普通变量多了 __Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131

    • __Block_byref_zj_name_0 结构体中多了 __Block_byref_id_object_copy__Block_byref_id_object_dispose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//********编译后的zj_name********
__Block_byref_zj_name_0 zj_name =
{(void*)0,
(__Block_byref_zj_name_0 *)&zj_name,
33554432,
sizeof(__Block_byref_zj_name_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_0)};

//********__Block_byref_zj_name_0结构体********
struct __Block_byref_zj_name_0 {
void *__isa;
__Block_byref_zj_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*); // 5*8 = 40
NSString *zj_name;
};

//********__Block_byref_id_object_copy_131********
//block自身拷贝(_Block_copy) -- __block bref结构体拷贝(_Block_object_assign) -- _Block_object_assign中对外部变量(存储在bref)拷贝一份到内存
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// dst 外部捕获的变量,即结构体 - 5*8 = 40,然后就找到了zj_name(zj_name在bref初始化时就赋值了)
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

//********__Block_byref_id_object_dispose_131********
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
  • 通过 libclosure-74 可编译源码断点调试,关键方法的执行顺序为:_Block_copy -> _Block_byref_copy -> _Block_object_assign,正好对应上述的三层copy

综上所述,block是如何取到 zj_name 的?

  • 通过 _Block_copy 方法,将block拷贝一份至堆区

  • 通过 _Block_object_assign 方法正常拷贝,因为__block修饰的外界变量在底层是 Block_byref 结构体

  • 发现外部变量还存有一个对象,从bref中取出相应对象zj_name,拷贝至block空间,才能使用(相同空间才能使用,不同则不能使用)。最后通过 内存平移 就得到了 zj_name,此时的zj_name 和 外界的zj_name是同一片内存空间(从_Block_object_assign 方法中的 *dest = object 看出)

三层copy总结

总上所述,block三层拷贝是指以下三层:

  • 【第一层】通过 _Block_copy 实现对象的 自身拷贝,从 栈区 拷贝到 堆区

  • 【第二层】通过 _Block_byref_copy 方法,将对象拷贝为 Block_byref 结构体类型

  • 【第三层】通过 _Block_object_assign 方法,对 __block 修饰的 当前变量的拷贝

注:只有 __block修饰 的对象,block即copy才有三层

_Block_object_dispose分析

同一般的 retainrelease 一样,_Block_object_dispose 其本质主要是 retain,所以对应的还有一个 release,即 _Block_object_dispose 方法,其源码实现如下,也是通过区分block种类,进行不同释放操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents 当Blocks或Block_byrefs持有对象时,其销毁助手例程将调用此入口点以帮助处置内容
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF://__block修饰的变量,即bref类型的
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK://block类型的变量
_Block_release(object) ;
break;
case BLOCK_FIELD_IS_OBJECT://普通对象
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
  • 进入 _Block_byref_release 源码,主要就是对象、变量的释放销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void _Block_byref_release(const void *arg) {
//对象强转为Block_byref类型结构体
struct Block_byref *byref = (struct Block_byref *)arg;

// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;//取消指针引用

if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//是否有拷贝辅助函数
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);//销毁拷贝对象
}
free(byref);//释放
}
}
}

所以,综上所述,Block的 三层copy 的流程如下图所示:

  • Post title:OC底层原理30:Block底层原理
  • Post author:张建
  • Create time:2021-04-22 14:46:32
  • Post link:https://redefine.ohevan.com/2021/04/22/OC底层原理/OC底层原理30:Block底层原理/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.