本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
当block 内部访问了对象类型的auto变量时
- 如果block是在栈空间,如果访问到了auto变量,不论此auto变量是被强制指针指向着或者被弱指针指向着,block内部都肯定不会对auto变量产生强引用;(因为block本身都是随时可能被销毁的,就完全没必要强引用外面的变量)
- (void)test {
XYHPerson *person = [[XYHPerson alloc] init];
person.age = 10;
^{
NSLog(@"---------%d", person.age);
};
}
如果block是在堆空间
1> 会调用block内部的copy函数
2> copy函数内部会调用_Block_object_assign函数
3> _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak 、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用如果block从堆空间移除
1>会调用block内部的dispose函数
2>dispose函数内部会调用_Blocl_object_dispose函数
3> _Blocl_object_dispose函数会自动释放(引用计数器减一)引用的auto变量(类似于release)
// Runtime copy/destroy helper functions (from Block_private.h)
运行时 复制、销毁 函数 在Block_private.h文件中
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
- copy 函数 栈上的block赋值到堆时调用
- dispose 函数 堆上的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};
可以看到__main_block_desc_0中相对于访问基本数据类型多了
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
这两个函数;
那么为什么会多这两个函数呢 ?
原因是因为当block内部需要访问的变量是一个auto对象,对象是通过引用计数管理的,而这两个函数的作用就是对其进行内存管理的。同时需要注意基本数据类型是系统自动管理。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
XYHPerson * person = [[XYHPerson alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"-------%@", person);
});
NSLog(@"touchesBegan:withEvent:");
}
上述情况person什么时候被释放 ?为什么
答:person会在3秒过后被释放(在XYHPerson初始化的时间忽略不计的情况下);
原因:dispatch_after() 是GCD的API , 且是以block为方法参数的,系统对该block做了copy操作,那么block内部访问的person对象会引用计数器加一,只有在block结束后才会对其引用计数器减一,这个时候person被释放。
-->dispatch_block_t block:
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);
两次打印,哪一个先输出 ?为什么
答:先输出 touchesBegan:withEvent: ; dispatch_after()是异步3秒后回到主线程执行;
如果在上述代码中假如了耗时操作,还会在3秒后打印person吗?如果不会,为什么 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
XYHPerson * person = [[XYHPerson alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"-------%@", person);
});
NSInteger count = 0;
for (long long i = 0; i < 5000000000; i++)
{
count ++;
}
NSLog(@"touchesBegan:withEvent:");
}
答:不会;原因:耗时操作和延迟操作都是在主线程,dispatch_after又是异步,所以耗时操作先执行,在耗时操作没执行完之前时无法暂停来执行其他任务的。
准确的说:dispatch_after的真正含义是在3秒后把任务添加进队列中,并不是表示在3秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。
如果把dispatch_get_main_queue()替换成其他队列,结果会有什么不同 ?如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
XYHPerson * person = [[XYHPerson alloc] init];
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), queue, ^{
NSLog(@"我是延迟执行-------%@", person);
});
NSLog(@"耗时任务开始");
NSInteger count = 0;
for (long long i = 0; i < 5000000000; i++)
{
count ++;
}
NSLog(@"耗时任务结束");
}
答:会在3秒钟的时候来到dispatch_after中执行任务;
直观的查看输出结果:
2020-03-12 17:30:13.742847+0800 -测试[8613:2014102] 耗时任务开始
2020-03-12 17:30:16.802072+0800-测试[8613:2014252] 我是延迟执行-------< XYHPerson: 0x60000390c3b0>
2020-03-12 17:30:25.724886+0800-测试[8613:2014102] 耗时任务结束
2020-03-12 17:30:25.725023+0800-测试[8613:2014102] XYHPerson - dealloc
可以看出 17:30:13 ~ 17:30:16 相差时间基本是为3秒 !
下面代码中person什么时候被释放 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
XYHPerson * person = [[XYHPerson alloc] init];
__weak XYHPerson *weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", person);
});
});
}
答:person大概在3秒后被释放;这其中dispatch_after的block中再次执行dispatch_after函数;由于在第二次执行延迟操作中对auto变量是强引用,所以person会在该强引用执行后释放;这样的使用我们在开发中经常用到,比如在网络请求时我们的block种需要用到self,一般会在block外部先对其进行一次弱引用,之后再block内部再进行强引用。如下:
kWeakSelf(self);
[BRStringPickerView showStringPickerWithTitle:@"" dataSource:list defaultSelValue:@"" isAutoSelect:NO themeColor:kColorFromHex(0x999999) resultBlock:^(id selectValue) {
kStrongSelf(self);
self.jdPostTypelb.text = selectValue;
NSLog(@"--------------- %@",selectValue);
} cancelBlock:^{
}];
总结 :person什么时候被释放取决于编译器,而编译器的判断依据是看dispatch_after函数里有没有强引用person,如果有,那么强引用的代码什么时候执行完毕什么时候就是释放的时机 !至于dispatch_after内部再次执行延迟操作、打印输出这些是被看作一个整体的,在这中间有强引用person,所以会在此之后释放。