[关闭]
@iwanglong 2019-01-07T09:49:05.000000Z 字数 5791 阅读 407

block的原理

iOS


现在面试中肯定会问到block相关的知识点,平时用block的地方也还是挺多的,为了很好的应对面试,这里将一些常问的都整理一下。

  • block原理是怎样的,本质是什么?
  • __block的作用是什么?有什么使用注意点?
  • block的属性修饰词为什么是copy?使用block有哪些使用注意?
  • block在修改NSMutableArray,需不需要添加__block?

我们带着问题来研究一下:

一、block原理是怎样的,本质是什么?

block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。

  • isa指针在哪儿呢?
    我们通过OC代码转成C++代码查看源码可以知道,block定义中调用__main_block_imp_0函数,这个结构体中包含结构体__block_impl、结构体__main_block_desc_0、以及自定义的变量age。以及一个__main_block_imp_0函数; 那么isa在哪儿呀,就在__block_impl这个结构体中啦,这个结构体中存放着将block代码块中代码封装成__main_block_func_0函数的地址
  • 本质
    本质就是__main_block_impl_0结构体类型。

变量捕获

一张图

总结:局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获

block的类型

那么什么时候ARC会自动将block进行一次copy呢?

block捕获对象的一些总结

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. Person *p = [[Person alloc] init];
  3. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  4. NSLog(@"%@",p);
  5. });
  6. NSLog(@"-------touchesBegan------end");
  7. }
  8. 2019-01-07 10:20:09.874108+0800 Autoreleasepool[33413:2118152] -------touchesBegan------end
  9. 2019-01-07 10:20:13.169933+0800 Autoreleasepool[33413:2118152] <Person: 0x60800000db40>
  10. 2019-01-07 10:20:13.170205+0800 Autoreleasepool[33413:2118152] ----Person-----dealloc

在ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. Person *p = [[Person alloc] init];
  3. __weak Person *weakP = p;
  4. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  5. NSLog(@"%@",weakP);
  6. });
  7. NSLog(@"-------touchesBegan------end");
  8. }
  9. 2019-01-07 10:24:19.531902+0800 Autoreleasepool[33621:2121142] -------touchesBegan------end
  10. 2019-01-07 10:24:19.532074+0800 Autoreleasepool[33621:2121142] ----Person-----dealloc
  11. 2019-01-07 10:24:22.531949+0800 Autoreleasepool[33621:2121142] (null)

block中对waekP为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. Person *p = [[Person alloc] init];
  3. __weak Person *weakP = p;
  4. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  5. NSLog(@"------weakP-----:%@",weakP);
  6. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  7. NSLog(@"------p-----:%@",p);
  8. });
  9. });
  10. NSLog(@"-------touchesBegan------end");
  11. }
  12. 2019-01-07 10:25:51.412902+0800 Autoreleasepool[33705:2122604] -------touchesBegan------end
  13. 2019-01-07 10:25:52.508281+0800 Autoreleasepool[33705:2122604] ------weakP-----:<Person: 0x60400000e110>
  14. 2019-01-07 10:25:55.508562+0800 Autoreleasepool[33705:2122604] ------p-----:<Person: 0x60400000e110>
  15. 2019-01-07 10:25:55.508812+0800 Autoreleasepool[33705:2122604] ----Person-----dealloc

person对象在4s后销毁,在第二个dispatch_after内部还在强引用person对象,因此需要在第二个block执行完毕以后才会对person对象释放。作为对比,请看下边的例子

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  2. Person *p = [[Person alloc] init];
  3. __weak Person *weakP = p;
  4. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  5. NSLog(@"------p-----:%@",p);
  6. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  7. NSLog(@"------weakP-----:%@",weakP);
  8. });
  9. });
  10. NSLog(@"-------touchesBegan------end");
  11. }
  12. 2019-01-07 10:29:31.948325+0800 Autoreleasepool[33890:2125189] -------touchesBegan------end
  13. 2019-01-07 10:29:33.045081+0800 Autoreleasepool[33890:2125189] ------p-----:<Person: 0x60400001bd80>
  14. 2019-01-07 10:29:33.045356+0800 Autoreleasepool[33890:2125189] ----Person-----dealloc
  15. 2019-01-07 10:29:36.045392+0800 Autoreleasepool[33890:2125189] ------weakP-----:(null)

person对象1s后销毁了,所以第二个block内打印的对象为null。

block内部如何修改外部变量的值?

平时用的时候咱们都知道加上__block修饰就行了啊,是的,没错。

block的内存管理

当block内存在栈上时,并不会对__block变量产生内存管理。当blcok被copy到堆上时
会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)
首先通过一张图看一下block复制到堆上时内存变化
copy1
copy2

在block中__weak __strong的使用

__weak修饰的对象被Block引用,不会影响对象的释放,而__strong在Block内部修饰的对象,会保证,在使用这个对象在scope内,这个对象都不会被释放,出了scope,引用计数就会-1,且__strong主要是用在多线程运用中,若果只使用单线程,只需要使用__weak即可

iOS底层原理总结 - 探寻block的本质

__weak __strong 在block中使用
OC中Block使用了__weak和__strong依然不会循环引用原因

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注