iOS基础深入补完计划--Block相关原理探究

前文地址:《iOS基础深入补完计划》

在前文、我们提到了property中的关键字copy可以修饰block。
那么、block究竟有哪些特殊。
block该怎样声明
__block、__weak、__strong是如何工作的呢。
__block又是否可以解决循环引用呢?

首先、关于block的作用域

  • 总的来说。block默认是存档在栈中(作用于被限制在当前上下文(函数, 方法...))、可能被随时回收(比如代码块执行完毕、会将块内变量内存回收)。通过copy操作可以使其在堆中保留一份,、相当于一直强引用着。以此保证在执行block对象时、程序不会因为执行一个空对象而崩溃。
举个例子
   @interface ViewController ()
   
   @property (nonatomic, copy) void(^myblock)();
   
   @end
   
   @implementation ViewController
   
   - (void)viewDidLoad {
       [super viewDidLoad];
       // Do any additional setup after loading the view, typically from a nib.
   
       [self test];//当此代码结束时,test函数中的所有存储在栈区的变量都会被系统释放。
       self.myblock();
   
   }
   
   - (void)test {
       int n = 5;
       [self setMyblock:^{
           NSLog(@"%d",n);
       }];
   }
之后、把copy声明换成assign。

EXC_BAD_ACCESS、使用了一个野指针。


iOS基础深入补完计划--Block相关原理探究_第1张图片
屏幕快照 2017-12-04 下午5.53.09.png
此外、我们还可以进一步确认崩溃的原因。
iOS基础深入补完计划--Block相关原理探究_第2张图片
屏幕快照 2017-12-05 上午10.12.01.png
之后、到底是不是因为局部变量被释放、导致的崩溃?
  • 用成员变量试试。


    iOS基础深入补完计划--Block相关原理探究_第3张图片
    WechatIMG210.jpeg

    看来无论是否使用成员变量、内部的变量指针都会被释放


    iOS基础深入补完计划--Block相关原理探究_第4张图片
    WechatIMG211.jpeg
结论:
  • copy声明会把变量在block保存一份指针变量、指向原对象地址。保证block内部所引用变量不会被自动释放而造成崩溃。
  • assign声明的block、内部引用的变量(无论是否是局部变量)在出了赋值当前作用域之后、会被释放。导致崩溃。
除此之外:
  • block声明为成员变量、用strong修饰。也是没有问题的、作用和copy一样。
  • block声明为成员变量、不加任何修饰。效果也和copy/strong一样。
  • block声明为局部变量、不加任何修饰。效果也和copy/strong一样。

关于copy之后block的一些特性

会对其内部的使用对象引用计数+1

这也是为什么需要用__weak去修饰self以防止循环引用的原因。
更多的解释可以参阅《__weak与__block区别,深层理解两者区别》、《深入分析 Objective-C block、weakself、strongself 实现原理》

原理总结下来就是

__weak会新开辟一个变量指针、指向被修饰的变量地址、但不会对该变量增加引用
当被修饰变量被释放、__weak变量指针依旧会指向原有变量地址、而该地址则会返回空。
所以需要用__strong在block内部修饰、让block内的变量不会在中途被释放掉、保证block内容一致性。

iOS基础深入补完计划--Block相关原理探究_第5张图片
WechatIMG204.jpeg


默认情况下、内外部的变量数据不会互通。

   int a = 1;
   
   int (^backValue)(int) = ^(int num) {
       return num+a;
   };
   
   a += 1;//这次+=1不会被传递到block中a上。
   int b = backValue(1);
   NSLog(@"b-->%d", b);
  //输出结果b-->2
  • 当然、我们可以__block来修饰这个a、使其在block内部的指针也可以被修改


    iOS基础深入补完计划--Block相关原理探究_第6张图片
    屏幕快照 2017-12-04 下午6.11.00.png
  • 为什么用__block修饰就可以修改外部的变量、或者将外部变量的修改传递进block内了?

先写结论、不愿意细看可以跳过。

  • 无论是普通变量、还是OC变量。在不使用__block的情况下、无法对其本身进行修改。

因为二者、都只会在block执行代码中copy一份值/指向该对象地址的指针。即使修改了这个值/指针指向、也无法对外部造成影响。所以编译器干脆就禁止这个操作了(虽然你可以通过直接操作指针的方式修改、但是被修改的依旧只是block内部的指针变量而已)

  • OC对象在不使用__block修饰的情况下、可以修改其属性。

自身的指针会被捕获到block代码块中、借此操作该指针指向地址所属对象的属性。(这也是为什么在block里可以修改self.xxx属性的原因)。

  • OC对象以及普通变量在使用__block修饰的情况下、可以对自身进行修改。

会将原变量的指针从栈移动至堆区。由block对象进行捕获保存、并借此进行修改。


以下是相关分析

这里有一篇文章、借助编译后的block阐释了__block的工作原理
《你真的理解__block修饰符的原理么》

  • 简而言之、block捕获普通变量其实将变量的值捕获到了block执行块中。这个已经与原变量无关。(由于无法直接获得原变量、技术上无法实现修改、所以编译器直接禁止了)。
    iOS基础深入补完计划--Block相关原理探究_第7张图片
    屏幕快照 2017-12-04 下午6.36.49.png

    iOS基础深入补完计划--Block相关原理探究_第8张图片
    block内部的指针指向、与外部完全分离
  • __block声明的变量、在被block捕获时。会被block以指针变量的形式保存在自身处于堆中结构体内。同时、原对象的指针地址也被修改到了堆中。既然变量指针内存地址相同、就代表是同一个指针、自然可以同步修改。

iOS基础深入补完计划--Block相关原理探究_第9张图片
__block之后的效果

具体可见: 《iOS中__block 关键字的底层实现原理》、 《一篇文章看懂iOS代码块Block》

  • 当然、还有个其他情况。就是OC对象的属性。

当block执行代码需要捕获的是OC对象时、block会自动在block代码块中copy一个对象指针(普通对象是copy值)、指向该对象当前地址。
就会出现如下情况


iOS基础深入补完计划--Block相关原理探究_第10张图片
屏幕快照 2017-12-04 下午6.57.07.png
不过、并不允许你为这个存在栈中的变量指针直接重新指定一个新的指向。除非使用__block将这个对象的地址直接移动到堆中。

此外、还做了另外一个实验。


iOS基础深入补完计划--Block相关原理探究_第11张图片
屏幕快照 2017-12-04 下午8.18.27.png
这也证明、即便是OC对象也仅仅是捕获了当前指针的指向。如果新生成了一个对象修改了外部的对象的指针指向地址、里面继续修改也没用了。

关于block的循环引用

  • __block是否可以防止循环引用。

可以、只需要在block内部根据需要将变量置空。缺点是需要手动调用一次block

 __block id tmp = self;
 self.block = ^{
     tmp = nil;
 };
 self.block();
需要注意的是。在tmp被block的捕获的时候、和__weak一样是不会增加tmp的引用计数的。之所以要手动释放、是因为在__block id tmp = self;时、tmp对self进行了一次强引用。需要消除的是这次引用。而且、当tmp被置nil之后、下次block调用将会得到一个空地址。但原对象不受此影响。
iOS基础深入补完计划--Block相关原理探究_第12张图片
WechatIMG207.jpeg

最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

你可能感兴趣的:(iOS基础深入补完计划--Block相关原理探究)