文章目录
OC对象 - Block-对象类型的auto变量
前言
本章主要了解Block
访问对象类型
的auto变量
时,底层主要做了什么,对变量有什么影响
1. Block内部访问了对象类型的auto变量
当block
内部访问了对象类型的auto
变量时,此时block可能在栈上
,也可能在堆上
,不同类型
的block所表现的动作是不一样的
1.1 基础代码
创建一个ZSXPerson
类,在- (void)dealloc
打印一下,这样我们可以清楚看到ZSXPerson
对象什么时候销毁
@interface ZSXPerson : NSObject
@end
@implementation ZSXPerson
- (void)dealloc {
[super dealloc];
NSLog(@"ZSXPerson --- %s", __func__);
}
@end
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
ZSXPerson *person = [[ZSXPerson alloc] init];
}
NSLog(@"-----");
}
return 0;
}
在{}
内,初始化一个ZSXPerson
对象,然后在在{}
外打了断点
可以看到person
对象出了{}
会马上销毁
接下来我们在此基础上,尝试不同 block 对person
对象销毁时机的影响
1.2 Block访问person对象
增加一个block,内部访问person对象
ZSXBlock block;
{
ZSXPerson *person = [[ZSXPerson alloc] init];
person.age = 10;
block = ^ {
NSLog(@"%d", person.age);
};
}
NSLog(@"block - %@", [block class]);
NSLog(@"-----");
此时断点停留在{}
之后,按理说 person 已经出了作用域了,但实际却没有销毁
我们断点继续往下走
出了@autoreleasepool
之后,person
才销毁。这时候刚好 block 也已经出了作用域,block
是会销毁的。因此,block
里面对person
有强引用,所以出了第一个{}
的时候,虽然person
已经走出作用域,但是此时block
还在作用域内,它还持有person
,所以person
并不会释放
1.2.1 查看底层实现
block访问对象类型的auto变量后
- block结构体中持有
person
成员的指针 __main_block_desc_0
结构体中多了copy
和dispose
两个函数。这两个函数是用来管理block
所持有变量的持有关系的
因为block里面持有了对象,对象本身在内存中是通过引用计数
来管理内存的,因此block也需要对其负责内存管理
1.3 NSStackBlock
类型block访问对象类型
NSStackBlock
类型block本身就是随时可能释放的,所以NSStackBlock
类型的block没有必要强持有访问对象
1.3.1 修改代码
代码中我们不使用
strong变量接收block,这时候的block就是NSStackBlock
类型的
ZSXBlock block;
{
ZSXPerson *person = [[ZSXPerson alloc] init];
person.age = 10;
^ {
NSLog(@"%d", person.age);
};
}
NSLog(@"block - %@", [block class]);
NSLog(@"-----");
打印结果:
person
出了作用域马上就释放了,因此可以说明这时候的block
并没有持有person
。
1.4 __weak
修饰
使用__weak
修饰person
变量
ZSXBlock block;
{
ZSXPerson *person = [[ZSXPerson alloc] init];
person.age = 10;
__weak typeof(person) weakPerson = person;
block = ^ {
NSLog(@"%d", weakPerson.age);
};
}
NSLog(@"block - %@", [block class]);
NSLog(@"-----");
这时候又会发现:
person
出了作用域就销毁了block
是堆上的__NSMallocBlock__
类型
这是因为,底层实现中
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*/);}
_Block_object_assign
函数会根据
auto变量的修饰符
(__strong、__weak、__unsafe_unretained)做出相应
的操作,形成强引用
(retain)或者弱引用
2. 总结
当block
内部访问了对象类型的auto
变量时
-
如果block是在
栈上
,将不会
对auto变量产生强引用 -
如果block被
拷贝到堆上
- 会调用block内部的
copy
函数 - copy函数内部会调用
_Block_object_assign
函数 - _Block_object_assign函数会根据auto变量的
修饰符
(__strong、__weak、__unsafe_unretained)做出相应
的操作,形成强引用
(retain)或者弱引用
- 会调用block内部的
-
如果block从堆上
移除
- 会调用block内部的
dispose
函数 - dispose函数内部会调用
_Block_object_dispose
函数 - _Block_object_dispose函数会
自动释放
引用的auto变量(release)
- 会调用block内部的
3. 拓展
block作为GCD API的方法参数时,系统会帮我们
copy
到堆上
如下代码,打印的是什么
3.1 aoto对象捕获
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ZSXPerson *person = [[ZSXPerson alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", person);
});
NSLog(@"touchesBegan:withEvent");
}
打印结果:
3.1.1 分析
black
会捕获person
,直到GCD
执行完,block
和person
一起释放
3.2 __weak对象
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ZSXPerson *person = [[ZSXPerson alloc] init];
__weak typeof(person) weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"-------------%@", weakPerson);
});
NSLog(@"touchesBegan:withEvent");
}
打印结果:
3.2.1 分析
- 点击屏幕后,
person
立刻销毁了 - 2秒后
black
执行
对于弱引用
的对象,black
内部也是弱引用
,所以person
出了作用域就销毁了
3.3 嵌套
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ZSXPerson *person = [[ZSXPerson alloc] init];
__weak typeof(person) weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakPerson-------------%@", weakPerson);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person-------------%@", person);
});
});
NSLog(@"touchesBegan:withEvent");
}
打印结果:
3.3.1 分析
- 点击屏幕
- 1秒后执行第一个定时器
- 2秒后执行第二个定时器
person
等到两个定时器都执行完,也就是3秒后才销毁
再来一个例子:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ZSXPerson *person = [[ZSXPerson alloc] init];
__weak typeof(person) weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person-------------%@", person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakPerson-------------%@", weakPerson);
});
});
NSLog(@"touchesBegan:withEvent");
}
打印结果:
3.3.2 分析
- 点击屏幕
- 1秒后执行第一个定时器,同时
person
也销毁了 - 2秒后执行第二个定时器
不管嵌套方式怎么样,person
不会被提前销毁。这是因为程序会看整体里面有没有使用强引用,保证在该释放的时候才释放
@oubijiexi