iOS block 捕获外部变量以及注意点

作者: 雪_晟 | 来源:发表于2017-03-30 15:44 被阅读1833次

    参考文章:深入研究Block捕获外部变量和__block实现原理
    做一些简单的总结说明:

    (1)对于四种非对象变量:

    • 自动变量(局部变量)
    • 静态变量
    • 静态全局变量
    • 全局变量

    首先: Block会捕获哪些变量?如果Block外面还有很多自动变量静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。

    其次:全局变量静态全局变量可以在Block内值被修改是为什么呢?全局变量静态全局变量在执行Block语法的时候,它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。

    然后:对于静态变量Block是如何捕获的呢?静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。在执行Block语法的时候,Block语法表达式所使用的静态变量的地址是被保存进了Block的结构体实例中,也就是Block自身中。所以能够在Block 内部修改静态变量的值。

    最后:为什么自动变量无法在Block内部修改值呢?类似静态变量,自动变量也是在执行Block语法的时候,被block捕获成为Block的结构体实例中,但是Block仅仅捕获了val的值,并没有捕获val的内存地址,所以在Block内部是无法修改自动变量的值。OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误。错误:Variable is not assignable(missing __block type specifier)
    后面会说明。

    总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

    1 传递内存地址

    对于对象变量,在被Block捕获后,在Block的结构体实例变量会增加一个指针,所以传递的是指针,所以成功改变了变量的值。

    2 __block 改变存储方式。

    _block修饰自动变量后,_block的变量也被转化成了一个结构体:__Block_byref_i_0,这个结构体有5个成员变量。

    struct __Block_byref_i_0 {
      void *__isa;   指针
    __Block_byref_i_0 *__forwarding; 指向自身类型的__forwarding指针
     int __flags; 标记flag
     int __size;大小
     int i; 变量值
    };
    

    MRC环境下,只有copy,_block才会被复制到堆上,否则,_block一直都在栈上,block也只是 _NSStackBlock,这个时候_forwarding指针就只指向自己了。
    ARC环境下,一旦Block赋值就会触发copy,_block就会copy到堆上,Block也是_NSMallocBlock。ARC环境下也是存在_NSStackBlock的时候,这种情况下,_block就在栈上

    在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型

    _forwarding指针初始化传递的是自己的地址,在执行Block的时候,堆上的Block会持有对象。 当我们把Block复制到堆上,堆上的Block也会持有_block.当Block释放的时候,_block没有被任何对象引用,也会被释放销毁。堆上的_forwarding指针也指向自己,只不过一个指针是_NSConcreteStackBlock,一个是_NSConcreteMallocBlock,两份_block ,栈上的_forwarding指针指向堆区的block,堆区的_forwarding指针指向原来的自己所以这样不管_block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。

    Block 分类

    OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
    先来说明一下3者的区别。

    • _NSConcreteStackBlock:
      只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
      StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

    • _NSConcreteMallocBlock:
      有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

    • _NSConcreteGlobalBlock:
      没有用到外界变量或只用到全局变量、静态变量的block为
      _NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
      没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。

    weakSelf StrongSelf 的使用

    解决循环应用的问题一定要分析清楚哪里出现了循环引用,只需要把其中一环加上weakSelf这类似的宏,就可以解决循环引用。_weak的实现原理,在原对象释放之后,_weak对象就会变成null,防止野指针。所以就输出了null了。

    那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?

    究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加_strong。

    在block里面使用的_strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。

     __weak typeof(student) weakSelf = student;
    
        student.study = ^{
            __strong typeof(student) strongSelf = weakSelf;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"my name is = %@",strongSelf.name);
            });
    
        };
    

    _block 与_weak 的区别

    1._block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
    2._weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
    3._block对象可以在block中被重新赋值,_weak不可以。

    相关文章

      网友评论

        本文标题:iOS block 捕获外部变量以及注意点

        本文链接:https://www.haomeiwen.com/subject/hzzuottx.html