美文网首页
Block源码分析

Block源码分析

作者: alvin_wang | 来源:发表于2017-12-26 15:46 被阅读21次
    1.什么是Block

    Block是带有自动变量(局部变量)的匿名函数。Block的内部数据结构如下:

    struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    
    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    • isa指针:指向表明该block类型的类。
    • flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。
    • reserved:保留变量,我的理解是表示block内部的变量数。
    • invoke:函数指针,指向具体的block实现的函数调用地址。
    • descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。
    • variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。
    2.block类型

    C/C++/OC编译的程序占用内存分布的结构如下:

    image.png
    • 栈区(stack):由系统自动分配,一般存放函数参数值、局部变量的值等。由编译器自动创建与释放。其操作方式后进先出、先进后出。
    • 堆区(heap):一般由程序员申请并指明大小,最终也由程序员释放。如果程序员不释放,程序结束时可能会由OS回收。对于堆区的管理是采用链表式管理的,操作系统有一个记录空闲内存地址的链表,当接收到程序分配内存的申请时,操作系统就会遍历该链表,遍历到一个记录的内存地址大于申请内存的链表节点,并将该节点从该链表中删除,然后将该节点记录的内存地址分配给程序。
    • 全局区/静态区:全局变量和静态变量存储在这个区域。程序结束后由系统释放。
    • 程序代码区:主要存放函数体的二进制代码。

    block按照不同的内存区域可以分为:

    • _NSConcreteStackBlock,存放在栈区。只用到外部局部变量、成员属性变量,且没有强指针引用的block。
      StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。
    • _NSConcreteMallocBlock,存放在堆区。有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制。
    • _NSConcreteGlobalBlock, 存放在全局/静态区。没有用到外界变量或只用到全局变量、静态变量的block,生命周期从创建到应用程序结束。
    int globalNum = 3;
    int main(int argc, const char * argv[]) {
        int a = 3;
        NSLog(@"this block is: %@",^{NSLog(@"I use nothing");});
        NSLog(@"this block is: %@",[^{NSLog(@"I use ivar %d",a);} copy]);
        NSLog(@"this block is: %@",^{NSLog(@"I use ivar %d",a);});
        NSLog(@"this block is: %@",^{NSLog(@"%d",globalNum);});
        return 0;
    }
    

    其输出结果如下:

    2017-12-26 09:01:27.668046+0800 BlockDemo[98660:1165488] this block is: <__NSGlobalBlock__: 0x100001050>
    2017-12-26 09:01:27.668246+0800 BlockDemo[98660:1165488] this block is: <__NSMallocBlock__: 0x100507f80>
    2017-12-26 09:01:27.668293+0800 BlockDemo[98660:1165488] this block is: <__NSStackBlock__: 0x7fff5fbfefe0>
    2017-12-26 09:01:27.668367+0800 BlockDemo[98660:1165488] this block is: <__NSGlobalBlock__: 0x1000010d0>
    

    注意:在ARC的作用下,block类型通过=进行传递时,会导致objc_retainBlock->_Block_copy->_Block_copy_internal方法链,导致__NSStackBlock__类型的block转换为__NSMallocBlock__类型。

    3. block的实现原理

    用clang工具重写为c++代码来探究一下block的内部实现。
    测试代码如下:

    typedef int(^blk)(int);
    
    int globalNum = 3;
    int main(int argc, const char * argv[]) {
        int a = 3;
        __block int blockVar = 3;
        static int staticNum = 3;
        blk blk = ^(int count) {
            NSLog(@"a = %d", a);
            staticNum --;
            globalNum --;
            blockVar --;
            return count * a * globalNum * staticNum;
        };
        a = 10;
        NSLog(@"%d", blk(1));
        return 0;
    }
    

    clang重写c++后的代码如下:

    typedef int(*blk)(int);
    
    int globalNum = 3;
    struct __Block_byref_blockVar_0 {
      void *__isa;
      __Block_byref_blockVar_0 *__forwarding;
     int __flags;
     int __size;
     int blockVar;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      int *staticNum;
      __Block_byref_blockVar_0 *blockVar; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_staticNum, __Block_byref_blockVar_0 *_blockVar, int flags=0) : a(_a), staticNum(_staticNum), blockVar(_blockVar->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static int __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {
      __Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
      int a = __cself->a; // bound by copy
      int *staticNum = __cself->staticNum; // bound by copy
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_v9qmb8t568sgwcjv8bj5ng380000gn_T_main_6b603d_mi_0, a);
        (*staticNum) --;
        globalNum --;
        (blockVar->__forwarding->blockVar) --;
        return count * a * globalNum * (*staticNum);
    }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    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};
    
    int main(int argc, const char * argv[]) {
        int a = 3;
        __attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 3};
        static int staticNum = 3;
        blk blk = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &staticNum, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
        a = 10;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_v9qmb8t568sgwcjv8bj5ng380000gn_T_main_6b603d_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 1));
        return 0;
    }
    

    我们看到block其实就是struct __main_block_impl_0 结构体,它由4个成员变量和一个构造函数组成。
    其中第一个成员变量是__block_impl结构体,指向的是__main_block_func_0函数。
    第二个成员变量是__main_block_desc_0结构体,负责管理block的内存管理。__main_block_copy_0__main_block_dispose_0就是利用OC的runtime进行内存管理。在Block中使用 __strong或者__block修饰符的id类型或对象类型的自动变量时,当block从栈复制到堆时,当对象需要retain的时候调用__main_block_copy方法增加引用计数,当需要释放的时候则调用__main_block_dispose方法释放对象。
    第三个是int变量。因为block内部引用了外部的自动变量,所以在block结构体中多了一个同类型的成员变量。
    第四个是捕获到的静态变量。
    第五个是__block修饰符的变量。具体介绍看下一节。

    __main_block_func_0包含了block的内部代码。其中包含了一个参数__cself,指向__main_block_impl_0,及匿名block自身。这种写法类似与OC中的方法消息传递。
    另外可以看到系统加的注释bound by copy,自动变量通过__cself->val方式捕获。局部变量传入的是值,静态变量传入的是地址。因此局部变量无法更改,而静态变量可以更改。同时全局变量由于作用域大,因此不需要传入就可以自由的在block内部更改。而__block修饰符的变量是通过bound by ref方式被捕获进来的,具体介绍看下一节。

    image.png
    4. __block 修饰符

    我们直到,当想要修改block外面的局部变量时,需要用__block修饰符。那么__block的原理是如何的呢。
    从上面的转换代码中可以看到,带有__block修饰符的局部变量被转换为一个结构体__Block_byref_blockVar_0。这个结构体有5个成员变量。

    • __isa,isa指针
    • __forwarding,指向自身类型的指针
    • __flags,标记
    • __size,结构体大小
    • __blockVar,局部变量
    struct __Block_byref_blockVar_0 {
      void *__isa;
      __Block_byref_blockVar_0 *__forwarding;
      int __flags;
      int __size;
      int blockVar;
    };
    

    从代码中我们可以看到blockVar->__forwarding->blockVar这样的方式来对__block修饰的变量进行操作,但是为什么要搞这么复杂呢?
    __forwarding 指针就是针对堆里的block。把原来指向自己的__forwarding指针指向堆上的__block变量。然后堆上的变量的__forwarding指向自己。

    image.png image.png
    5. Block中的循环引用

    循环引用,就是相互引用。比如A强引用B,B又强引用A。那么A和B的引用计数永远无法为0,造成内存泄漏。当然这种循环也可以是多个对象间的。


    image.png

    而对于block的循环引用,一般是一个对象引用block,而block内部又引用了这个对象。
    打破这种循环引用一般有两种方法:1.弱引用;2.主动释放


    image.png
    5.1 弱引用
    __weak typeof(self) weakSelf = self;
    self.blk = ^{
        NSLog(@"%@",weakSelf);
    };
    
    5.2 主动释放
    __block id blockSelf = self;
    self.blk = ^{
        NSLog(@"%@",blockSelf);
        blockSelf = nil;
    };
    self.blk();
    

    当然,弱引用和主动释放各有缺点:
    主动释放的缺点:

    • 必须执行block才能避免循环引用

    弱引用的缺点:

    • 不能保证self对象是否已经被销毁

    当然对于弱引用,可以利用weak-strong-dance来保证不会出现因为self释放引起问题。

    __weak typeof(self) weakSelf = self;
    self.blk = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",strongSelf);
    };
    
    6. 参考链接

    1.深入研究Block捕获外部变量和__block实现原理
    2.老司机出品——源码解析之从Block说开去
    3.A look inside blocks: Episode 1
    4.A look inside blocks: Episode 2

    相关文章

      网友评论

          本文标题:Block源码分析

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