• 最详细的block底层


    主要讲述的要点:

    1. block 干什么用的

    2. block 语法

    3. block 底层实现

    4. block 变量捕捉

    5. block 的种类、在存储空间中的存储位置

    6. block 循环引用

    7. __block 在ARC 中 与 在MRC 中的是否造成循环引用问题

    8. 栈block生命周期

    1.首先我们来说说block干什么用的?

    block英语中是"块"的意思, 对就是保存一块代码用的, 只不过Block是C语言中的一种扩充数据类型, 把一块代码保存到一个Block中, 当你用到的时候利用调用函数的方式, 函数名()调用, 做过其他面向对象语言开发的同学们很熟悉, 有点像匿名函数的感觉, 对它就是objective-c中的匿名函数。  注意: Block是预先准备好的.编译的时候就确定完的.

    2. block 语法

    注意: 如果没有参数的block也可以省略参数的括号, 只是参数的括号, 不是声明参数的括号 void (^name)() = ^{};

    3. block 底层实现

     block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。

    我们定义一个run方法, 里面实现一个testBlock. 注意: 下面block没有应用外部变量, 引用外部变量的block跟没有引用外部变量的block底层还是有点区别的后面会介绍到.

    void run() {
        void (^testBlock)() = ^{
            NSLog(@"BDTrip
    ");
        };
        testBlock();
    }

    我们借住clang编译器查看

    注意: 说明一下clang的基本语法

    打开终端Console/ iTerm --> 进入当前文件所在位置目录 --> clang -rewrite-objc main.m 默认是编译非ARC的代码 

                                                                         clang -rewrite-objc -fobjc-arc main.h 这个是编译成ARC代码

    下面的代码默认全是非ARC的, 如果是ARC会在下面标注

    // Block 基结构体(就是基类, 由于都是结构体你懂得, 所有的结构体都有这个属性), 存放Block 基本信息
    struct __block_impl {
        void *isa; // 指向block的类型, 它包含了一个isa指针, 也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。这也是为什么Block能当做id类型的参数进行传递。是libSystem提供的地址
        int Flags; // 标志变量.记录引用次数, 如果式第一次copy会复制到堆中, 之后在copy只是增加内部的引用计数而已, 所以这个变量很重要
        int Reserved; // 保留变量
        void *FuncPtr; // block实现的函数指针
    };
    // 保存Block上下文信息
    // 命名规则: __FuncName__block_func_blockInFuncNumber
    ///        FuncName: 所在方法名
    ///        blockInFuncNumber: 当前block在所在方法内属于第几个block
    struct __run_block_impl_0 {
        struct __block_impl impl; // block 基本信息
        struct __run_block_desc_0* Desc; // 描述信息
        __run_block_impl_0(void *fp, struct __run_block_desc_0 *desc, int flags=0) { // 结构体构造函数C++ 特有的
            impl.isa = &_NSConcreteStackBlock; // (栈)型Block
            impl.Flags = flags; // 标志变量,在实现block的内部操作时会用到
            impl.FuncPtr = fp; // block执行时调用的函数指针
            Desc = desc; // 描述信息
        }
    };
    // Block 内部实现
    // 命名规则: __FuncName__block_func_blockInFuncNumber
    ///        FuncName: 所在方法名
    ///        blockInFuncNumber: 当前block在所在方法内属于第几个block
    static void __run_block_func_0(struct __run_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6j_6wv50f5n44z0hbzk3kf4y6dc0000gn_T_main_d5fa75_mi_0);
    }
    // 描述信息
    // 命名规则: __FuncName__block_desc_blockInFuncNumber
    ///        FuncName: 所在方法名
    ///        blockInFuncNumber: block在所在方法内属于第几个block
    static struct __run_block_desc_0 {
        size_t reserved; // 保留字段
        size_t Block_size; // block大小 sizeof(struct __run_block_impl_0)
    } __run_block_desc_0_DATA = { 0, sizeof(struct __run_block_impl_0)}; // 初始化一个变量__run_block_desc_0_DATA 为了给 __run_block_impl_0 初始化的时候赋值用
    void run() {
        // 初始化一个无参函数指针testBlock 指向__run_block_impl_0类型的变量的地址. 看起来很麻烦, 我们来拆解这句代码
        /// __run_block_func_0 __run_block = __run_block_impl_0((void *)__run_block_func_0, &__run_block_desc_0_DATA);
        /// 解释: 利用结构体构造函数初始化一个结构体变量 __run_block
        /// void (*testBlock)() = ((void (*)())&__run_block);
        /// 解释: 利用一个函数指针(void (*)())把结构体变量的地址进行强转过后, 创建一个testBlock无参函数指针指向
        void (*testBlock)() = ((void (*)())&__run_block_impl_0((void *)__run_block_func_0, &__run_block_desc_0_DATA));
        
        // 调用Block的实现. 看起来也很麻烦, 我们来拆解这句代码
        /// void *funcBlock = ((__block_impl *)testBlock)->FuncPtr
        /// 第一步 因为现在testBlock 是函数指针( (void (*)())类型 ), 所以要把testBlock 强制转换成(__block_impl *)类型, 然后再取出block实现指针FuncPtr
        /// void(*testFuncBlock)(__block_impl *) = (void (*)(__block_impl *))funcBlock
        /// 第二步 block实现指针 强制转化成((void (*)(__block_impl *))类型指针
        /// testFuncBlock((__block_impl *)testBlock)
        /// 第三步 执行实现指针, 并且 把testBlock强制转(__block_impl *) 类型传入实现函数中执行
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    }

    上面是我们通过clang做的代码转换, 下面我们看看源码是什么样子, 源码保存在<block_private.h>这个文件当中, 网上也有地址 https://opensource.apple.com/source/libclosure/libclosure-38/Block_private.h

    struct Block_descriptor {
        unsigned long int reserved; // 保留字 
        unsigned long int size; // block大小
        void (*copy)(void *dst, void *src); // 1. C++ 栈上对象 2.OC对象 3. 其他block对象 4. __block修饰的辅助函数, 处理block范围外的变量时使用
        void (*dispose)(void *); // 1. C++ 栈上对象 2.OC对象 3. 其他block对象 4. __block修饰的辅助函数, 处理block范围外的变量时使用
    };
    
    struct Block_layout {
        void *isa; // 这也是为什么Block能当做id类型的参数进行传递。是libSystem提供的地址
        int flags; // reference标识: 传不同的值意义也不同: BLOCK_NEEDS_FREE(这个标志表明block需要释放,在release以及再次拷贝时会用到)、BLOCK_IS_GLOBAL全局block
        int reserved; // 保留字
        void (*invoke)(void *, ...); // block执行时调用的函数指
        struct Block_descriptor *descriptor; // block的描述信息
        // imported variables
    };

    注意: copy 与dispose 是引用外部变量之后才会涉及, 在下面变量捕捉会提及

    官方对其copy 与dispose解释如下

    A Block can reference four different kinds of things that require help when the Block is copied to the heap.
    1) C++ stack based objects
    2) References to Objective-C objects
    3) Other Blocks
    4) __block variables
    In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers.  The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign.  The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.
    The flags parameter of _Block_object_assign and _Block_object_dispose is set to
        * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
        * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
        * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
    If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16).
    So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.
    When  a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions.  Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor.  And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.
    So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
        __block id                   128+3
        __weak block id              128+3+16
        __block (^Block)             128+7
        __weak __block (^Block)      128+7+16
            
    The implementation of the two routines would be improved by switch statements enumerating the eight cases.

    简单的翻译就是, 部分被忽略

     当Block被复制到堆时后,Block可以引用需要帮助的四种不同类型的var。
     1) C++ stack based objects
        基于C ++栈的对象
     2) References to Objective-C objects
        引用Objective-C对象
     3) Other Blocks
        其他Blocks
     4) __block variables
        __block 修饰的变量
     
    在这些情况下,辅助函数由编译器合并用于Block_copy和Block_release,称为复制和处理助手。复制的助手发出调用c++ const基于c++栈对象的拷贝构造函数,并且将其余调用发送到运行时支持函数_Block_object_assign。处理助手调用了case 1的C++析构函数,其余的调用到_Block_object_dispose。
     
     The flags parameter of _Block_object_assign and _Block_object_dispose is set to
        * BLOCK_FIELD_IS_OBJECT (3),证实: objective-c 对象
        * BLOCK_FIELD_IS_BLOCK (7), 证实: 内部引用一个block
        * BLOCK_FIELD_IS_BYREF (8), 证实: 被__block修饰的变量
        * BLOCK_FIELD_IS_WEAK (16)  被__weak修饰的变量,只能被辅助copy函数使用
        * BLOCK_BYREF_CALLER (128)  block辅助函数调用(告诉内部实现不要进行retain或者copy)
     
     所以Block复制/处理助手只能生成3,7,8和24的四个标志值。
     *** 没有遇到过
     __block复制/处理助手将分别为对象和块生成3或7的标志值,BLOCK_FIELD_IS_WEAK(16)或适当的时候会为128或“in”,为以下几种可能性:
        __block id                   128+3
        __weak block id              128+3+16
        __block (^Block)             128+7
        __weak __block (^Block)      128+7+16

    当Block引用了 1. C++ 栈上对象 2. OC对象 3. 其他block对象 4. __block修饰的变量,并被拷贝至堆上时则需要copy/dispose辅助函数。辅助函数由编译器生成。
    除了1,其他三种都会分别调用下面的函数:

    void _Block_object_assign(void *destAddr, const void *object, const int flags);
    void _Block_object_dispose(const void *object, const int flags);
    当Block从栈赋值到堆时,使用_Block_object_assign函数,持有Block引用的对象。 当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block引用的对象。 

    4. 变量捕捉

    1. 访问全局变量. 

    我们用全局的block去访问全局的变量如下代码:

    // 全局block
    int i = 0;
    void (^globalkTestBlock)() = ^{
        NSLog(@"%d
    ", i);
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            i = 4;
            globalkTestBlock();
            return 0;
        }
    }

    结果

    用控制台解析出来结果可以看出打印结果为最后修改的值. 我们用clang看看底层实现

    // 全局变量
    int i = 0;
    
    struct __globalkTestBlock_block_impl_0 {
      struct __block_impl impl;
      struct __globalkTestBlock_block_desc_0* Desc;
      __globalkTestBlock_block_impl_0(void *fp, struct __globalkTestBlock_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock; // 全局block
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __globalkTestBlock_block_func_0(struct __globalkTestBlock_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6j_6wv50f5n44z0hbzk3kf4y6dc0000gn_T_main_a4687a_mi_0, i);
    }
    
    static struct __globalkTestBlock_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __globalkTestBlock_block_desc_0_DATA = { 0, sizeof(struct __globalkTestBlock_block_impl_0)};
    
    // 静态变量block变量
    static __globalkTestBlock_block_impl_0 __global_globalkTestBlock_block_impl_0((void *)__globalkTestBlock_block_func_0, &__globalkTestBlock_block_desc_0_DATA);
    
    void (*globalkTestBlock)() = ((void (*)())&__global_globalkTestBlock_block_impl_0);
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            i =4;
            ((void (*)(__block_impl *))((__block_impl *)globalkTestBlock)->FuncPtr)((__block_impl *)globalkTestBlock);
            return 0;
        }
    }

    可以看出,因为全局变量都是在静态数据存储区,在程序结束前不会被销毁,所以block直接访问了对应的变量,而没有在__globalkTestBlock_block_impl_0

    结构体中给变量预留位置。

    2. 局部变量

    我们用全局的block去访问全局的变量如下代码:

    void stackTestBlock();
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            stackTestBlock();
        }
        
        return 0;
    }
    
    void stackTestBlock() {
        int a = 10;
        void (^testBlock)() = ^{
            NSLog(@"%d
    ", a);
        };
        
        a = 20;
        testBlock();
    }

    用控制台解析出来结果可以看出打印结果为10. 我们用clang看看底层实现

    void stackTestBlock();
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            stackTestBlock();
        }
        
        return 0;
    }
    
    struct __stackTestBlock_block_impl_0 {
        struct __block_impl impl;
        struct __stackTestBlock_block_desc_0* Desc;
        int a;
        __stackTestBlock_block_impl_0(void *fp, struct __stackTestBlock_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    static void __stackTestBlock_block_func_0(struct __stackTestBlock_block_impl_0 *__cself) {
        int a = __cself->a; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6j_6wv50f5n44z0hbzk3kf4y6dc0000gn_T_main_80a2be_mi_0, a);
    }
    
    static struct __stackTestBlock_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    } __stackTestBlock_block_desc_0_DATA = { 0, sizeof(struct __stackTestBlock_block_impl_0)};
    
    void stackTestBlock() {
        int a = 10;
        void (*testBlock)() = ((void (*)())&__stackTestBlock_block_impl_0((void *)__stackTestBlock_block_func_0, &__stackTestBlock_block_desc_0_DATA, a));
        
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    }

    可以看出block引用的a 是值引用

    3.__block修饰的变量

    我们用全局的block去访问全局的变量如下代码: 

    void stackTestBlock();
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            stackTestBlock();
        }
        
        return 0;
    }
    
    void stackTestBlock() {
        __block int a = 10;
        void (^testBlock)() = ^{
            NSLog(@"%d
    ", a);
        };
        
        a = 20;
        testBlock();
    }

    用控制台解析出来结果可以看出打印结果为20. 我们用clang看看底层实现

    // block内部访问的一个外部变量时, 对这个外部变量的包装
    // 命名规则: __Block_byref_var_0
    ///        var: 外部变量名
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding; // 复制之前, 栈上 block_byref 的 forward 指向自身, 而栈上的 block 使用 block_byref->forward->val 就可以修改栈上的数据.
                                     // 复制之后,栈上的 block_byref 被复制到堆上, 而栈上的 block_byref 的 forward 被修改成指向堆上的数据, 而堆上 block_byref 的 forward 指向自身. 这样无论是栈的 block 或者堆的 block修改变量都是修改的堆中的变量, 保证数据的同步性.
     int __flags;
     int __size;
     int a;
    };
    
    // block 内置信息
    struct __stackTestBlock_block_impl_0 {
      struct __block_impl impl;
      struct __stackTestBlock_block_desc_0* Desc;
      __Block_byref_a_0 *a; // 外部变量结构体指针
      __stackTestBlock_block_impl_0(void *fp, struct __stackTestBlock_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // block 内部实现
    static void __stackTestBlock_block_func_0(struct __stackTestBlock_block_impl_0 *__cself) {
        // 取值从block内置信息指针 中把结构体(__Block_byref_a_0)a指针取出,
        __Block_byref_a_0 *a = __cself->a; // bound by ref
        // 然后再根据结构体a指针取出内部自身指针(就是__forwarding)最后得到a变量值
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6j_6wv50f5n44z0hbzk3kf4y6dc0000gn_T_main_1cc9a5_mii_0, (a->__forwarding->a));
    }
    
    // 将__Block_byref_a_0 copy 外部var到堆当中
    // 命名规则: __FuncName_block_copy_0
    ///        FuncName: 所在方法名
    // 参数:
    ///     dst: 堆上的block
    ///     src: 栈上的block
    static void __stackTestBlock_block_copy_0(struct __stackTestBlock_block_impl_0*dst, struct __stackTestBlock_block_impl_0*src) {
        /**
         * BLOCK_FIELD_IS_OBJECT (3), 证实: objective-c 对象 == 2进制 11
         * BLOCK_FIELD_IS_BLOCK (7),  证实: 内部引用一个block == 2进制 111
         * BLOCK_FIELD_IS_BYREF (8),  证实: 被__block修饰的变量 == 2进制 1000
         * BLOCK_FIELD_IS_WEAK (16) 被__weak修饰的变量,只能被辅助copy函数使用 == 2进制 10000
         * BLOCK_BYREF_CALLER (128) block辅助函数调用(告诉内部实现不要进行retain或者copy) == 2进制 10000000
         * 
         ** __block修饰的基本类型时会被包装为对象.
         */
        _Block_object_assign((void*)&dst->a, (void*)src->a, 8/* BLOCK_FIELD_IS_BYREF */);
    }
    
    // 释放copy到堆上Block引用的对象
    // 命名规则: __FuncName_block_dispose_0
    ///        FuncName: 所在方法名
    static void __stackTestBlock_block_dispose_0(struct __stackTestBlock_block_impl_0*src) {
        // _Block_object_dispose其他文件封装的函数, 上面有函数声明引用, 当堆上的Block被废弃时, 释放Block截获的对象
        _Block_object_dispose((void*)src->a, 8/* BLOCK_FIELD_IS_BYREF: 表示对象是否进行retain或copy */);
    }
    
    // block 描述信息
    static struct __stackTestBlock_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __stackTestBlock_block_impl_0*, struct __stackTestBlock_block_impl_0*);
      void (*dispose)(struct __stackTestBlock_block_impl_0*);
    } __stackTestBlock_block_desc_0_DATA = { 0, sizeof(struct __stackTestBlock_block_impl_0), __stackTestBlock_block_copy_0, __stackTestBlock_block_dispose_0};
    
    void stackTestBlock() {
        // 外部变量初始化
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        // a是一个结构体, 体内存放变量a值, 把结构体a地址储存到block内置信息里
        void (*testBlock)() = ((void (*)())&__stackTestBlock_block_impl_0((void *)__stackTestBlock_block_func_0, &__stackTestBlock_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        // 修改变量值根据结构体自身指针找到自己的内部变量a修改
        (a.__forwarding->a) = 20;
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    }

    由此看出为什么加上__block后会把地址传过去, 内部获取的是指针内部的值, 属于地址传递

    注意: 对象传的是地址, 所以对象地址不变, 操作地址内容是可以修改的

    5. block的种类、 block 在存储空间中的存储位置

    • _NSConcreteGlobalBlock(全局)全局的静态 block,不会访问任何外部变量
    • _NSConcreteStackBlock(栈)保存在栈中的 block,当函数返回时会被销毁
    • _NSConcreteMallocBlock(堆)保存在堆中的 block,当引用计数为 0 时会被销毁

    注意:虽然目前 ARC 编译器在设置属性时,已经替程序员复制了 block,但是定义 block时,仍然建议使用 copy 属性

     _NSConcreteGlobalBlock

    void (^globalkTestBlock)() = ^{
        NSLog(@"%s
    ", __func__);
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            stackTestBlock();
        }
        
        return 0;
    }

    利用clang 转换完的代码:

    struct __globalkTestBlock_block_impl_0 {
      struct __block_impl impl;
      struct __globalkTestBlock_block_desc_0* Desc;
      __globalkTestBlock_block_impl_0(void *fp, struct __globalkTestBlock_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock; // 全局
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

    我们可以看出block的isa指向了_NSConcreteGlobalBlock 控制台打印的也可以看出block在_NSConcreteGlobalBlock被创建

    _NSConcreteStackBlock

    void stackTestBlock();
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            stackTestBlock();
        }
        
        return 0;
    }
    
    void stackTestBlock() {
        int a = 10;
        void (^testBlock)() = ^{
            NSLog(@"%d
    ", a);
        };
        
        a = 20;
        testBlock();
    }

    利用clang 转换完的代码:

    struct __stackTestBlock_block_impl_0 {
        struct __block_impl impl;
        struct __stackTestBlock_block_desc_0* Desc;
        int a;
        __stackTestBlock_block_impl_0(void *fp, struct __stackTestBlock_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };

    我们可以看出这个block的isa指向了_NSConcreteStackBlock, 当然如果在MRC下也可以看到控制台查看打印类型, 如果在ARC下系统会自动copy到堆中, 所以打印出来的结果可以验证此结论

    ARC 下打印结果:

    MRC 下打印结果:

    _NSConcreteMallocBlock

    通常这种类型的block不会在源码中出现, 因为1个 block被创建, 才会将这个 block 复制到堆中, 上面其实已经出现了验证结果, 在ARC下栈中的block会默认copy到堆中. 而在MRC下根本无法看到这一场景, 只有在编译的时候才能确定block的所在位置

    接下来我们用代码看看一个block如何copy到堆中的

    // 复制block 或 对block var引用计数。如果真的复制,请调用复制助手(存在)。
    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        
        if (!arg) return NULL;
        
        aBlock = (struct Block_layout *)arg;
        // 当再次拷贝i时,则仅仅retain其引用计数
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        // 全局block不会copy到堆中而是在全局区所以直接返回
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
        
        // 它的一个栈block。copy到堆中。
        if (!isGC) {
            // 在堆中申请一个大小相同的block内存
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return (void *)0;
            // memmove用于从aBlock拷贝size个字节到result,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后aBlock内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
            memmove(result, aBlock, aBlock->descriptor->size);
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK);
            result->flags |= BLOCK_NEEDS_FREE | 1; // 注入 BLOCK_NEEDS_FREE标识, 以后在copy指会retain引用计数而已
            result->isa = _NSConcreteMallocBlock; // 改变isa指向_NSConcreteMallocBlock,即堆block类型
            if (result->flags & BLOCK_HAS_COPY_DISPOSE) { // 如果有用到辅助函数调用辅助函数 __stackTestBlock_block_copy_0 但是内部实现是调用 _Block_object_assign
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            return result;
        }
    }

    从以上代码以及注释可以很清楚的看出,函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock
    block主要的一些学问就出在栈中block向堆中block的转移过程中了。 

    6. block循环引用

    当我们在Block使用__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。 这样容易引起循环引用:

    下面我们先看一下循环引用例子: 

    原因: block没有引用过外部变量会在全局区当中, 但是当block引用外部变量时,会从栈区copy到堆区, 为什么会copy到堆区, 栈大家都知道系统自己维护, block执行完就会被释放, 所以需要copy到堆当中长期保存, 由程序员手动管理, 那么问题就来了, 程序员手动管理在内部又引用了其他对象, 而block在此对象中, 这样2个对象强引用彼此, 就造成了循环引用现象, 用一幅图很清晰解释清楚: 

     

    补充: 如果一个方法内部的block没用引用外部变量也属于全局block 

    所以我们需要对外界对象进行弱引用转换

    转换过后的模拟图

    7.__block 在ARC 中 与 在MRC 中的是否造成循环引用问题

    为什么要把这个问题单独提出来? 面试的时候会问到这个. 所以单独说说为什么.

    ARC:

    使用如下代码验证(ARC环境下)

    #import <Foundation/Foundation.h>
    #include <stdio.h>
    #define logFunc NSLog(@"%s
    ", __func__);
    
    typedef void(^TestBlocks)();
    @interface TTPerson : NSObject
    @property (nonatomic, copy) TestBlocks block;
    @end
    
    @implementation TTPerson
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            __block TTPerson *person = self;
            self.block = ^{
                NSLog(@"%@", person);
            };
        }
        return self;
    }
    
    - (void)dealloc {
        logFunc
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            TTPerson *person = [TTPerson new];
            person.block();
        }
        return 0;
    }

    运行结果如下: 

    可以看出明显在ARC中__block引起了循环引用问题__block 在ARC中会造成循环引用. 利用clang 查看一下底层代码

    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     TTPerson *__strong person;
    };

    可以看到变量用__strong修饰, 会对其内部引用计数加1, 明显的是对变量强引用操作

    MRC

    也使用代码验证. 

    #import <Foundation/Foundation.h>
    #define logFunc NSLog(@"%s
    ", __func__);
    
    typedef void(^TestBlocks)();
    @interface TTPerson : NSObject
    @property (nonatomic, copy) TestBlocks block;
    @end
    
    @implementation TTPerson
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            __block TTPerson *person = self;
            self.block = ^{
                NSLog(@"%@", person);
                
            };
        }
        return self;
    }
    
    - (void)dealloc {
        logFunc
        [super dealloc];
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            TTPerson *person = [TTPerson new];
            person.block();
            [person release];
        }
        return 0;
    }

    运行结果: 

    可以看出在MRC下__block是不引起循环引用问题的, 也借助clang助手.

    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     TTPerson *person;
    };

    可以看出MRC下没有__strong, 不会操作引用计数, 所以不会造成循环引用

    8. block的生命周期

    block是编译时候就需要写好的, 所以block是在编译器编译的时候copy到堆中的. 大家都知道globalblock属于全局随app生而生 死而死, 所以不考虑, 只考虑栈block

    下面我们一起看看block如何copy到堆中的. 

    首先系统在编译的时候会调用如下方法

    // 我的猜想程序编译的同时会调用这个方法
    void *_Block_copy(const void *arg) {
        return _Block_copy_internal(arg, WANTS_ONE);
    }

    内部又调用了_Block_copy_internal

    // 复制block 或 对block var引用计数。如果真的复制,请调用复制助手(存在)。
    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        
        if (!arg) return NULL;
        
        aBlock = (struct Block_layout *)arg;
        // 当再次拷贝i时,则仅仅retain其引用计数
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        // 全局block不会copy到堆中而是在全局区所以直接返回
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
        
        // 它的一个栈block。copy到堆中。
        if (!isGC) {
            // 在堆中申请一个大小相同的block内存
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return (void *)0;
            // memmove用于从aBlock拷贝size个字节到result,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后aBlock内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
            memmove(result, aBlock, aBlock->descriptor->size);
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK);
            result->flags |= BLOCK_NEEDS_FREE | 1; // 注入 BLOCK_NEEDS_FREE标识, 以后在copy指会retain引用计数而已
            result->isa = _NSConcreteMallocBlock; // 改变isa指向_NSConcreteMallocBlock,即堆block类型
            if (result->flags & BLOCK_HAS_COPY_DISPOSE) { // 如果有用到辅助函数调用辅助函数 __stackTestBlock_block_copy_0 但是内部实现是调用 _Block_object_assign
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            return result;
        }
    }

    当一个对象再次引用不会再次copy会对其内部的引用计数累加

    // 当再次拷贝i时,则仅仅retain其引用计数
    static int latching_incr_int(int *where) {
        while (1) {
            int old_value = *(volatile int *)where;
            if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
                return BLOCK_REFCOUNT_MASK;
            }
            if (OSAtomicCompareAndSwapInt(old_value, old_value+1, (volatile int *)where)) {
                return old_value+1;
            }
        }
    }

    此方法就是编译器编译的时候把block copy到堆当中, 并且可以看出内部判断了是否有辅助函数的支持, 如果有会调用copy方法引用外部变量, copy方法是编译器生成block 结构体描述的时候生成的

    // 将__Block_byref_a_0 copy 外部var到堆当中
    // 命名规则: __FuncName_block_copy_0
    ///        FuncName: 所在方法名
    // 参数:
    ///     dst: 堆上的block
    ///     src: 栈上的block
    static void __stackTestBlock_block_copy_0(struct __stackTestBlock_block_impl_0*dst, struct __stackTestBlock_block_impl_0*src) {
        /**
         * BLOCK_FIELD_IS_OBJECT (3), 证实: objective-c 对象 == 2进制 11
         * BLOCK_FIELD_IS_BLOCK (7),  证实: 内部引用一个block == 2进制 111
         * BLOCK_FIELD_IS_BYREF (8),  证实: 被__block修饰的变量 == 2进制 1000
         * BLOCK_FIELD_IS_WEAK (16) 被__weak修饰的变量,只能被辅助copy函数使用 == 2进制 10000
         * BLOCK_BYREF_CALLER (128) block辅助函数调用(告诉内部实现不要进行retain或者copy) == 2进制 10000000
         * 
         ** __block修饰的基本类型会被包装为指针对象.
         */
        _Block_object_assign((void*)&dst->a, (void*)src->a, 8/* BLOCK_FIELD_IS_BYREF */);
    }
    
    // 释放copy到堆上的Block对象
    // 命名规则: __FuncName_block_dispose_0
    ///        FuncName: 所在方法名
    static void __stackTestBlock_block_dispose_0(struct __stackTestBlock_block_impl_0*src) {
        // _Block_object_dispose其他文件封装的函数, 上面有函数声明引用, 当堆上的Block被废弃时, 释放Block截获的对象
        _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF: 表示对象是否进行retain或copy, 具体代表什么不详*/);
    }
    
    // block 描述信息
    static struct __stackTestBlock_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __stackTestBlock_block_impl_0*, struct __stackTestBlock_block_impl_0*);
      void (*dispose)(struct __stackTestBlock_block_impl_0*);
    } __stackTestBlock_block_desc_0_DATA = { 0, sizeof(struct __stackTestBlock_block_impl_0), __stackTestBlock_block_copy_0, __stackTestBlock_block_dispose_0};

    我们可以看出__stackTestBlock_block_copy_0 就是copy方法, 其内部又调用了 _Block_object_assign 方法

    /**
     当Block被复制到堆时后,Block可以引用需要帮助的四种不同类型的var。
     1) C++ stack based objects
        基于C ++栈的对象
     2) References to Objective-C objects
        引用Objective-C对象
     3) Other Blocks
        其他Blocks
     4) __block variables
        __block 变量
     
     在这些情况下,辅助函数由编译器合并用于Block_copy和Block_release,称为复制和处理助手。复制的助手发出调用c++ const基于c++栈对象的拷贝构造函数,并且将其余调用发送到运行时支持函数_Block_object_assign。处理助手调用了case 1的C++析构函数,其余的调用到_Block_object_dispose。
     
     The flags parameter of _Block_object_assign and _Block_object_dispose is set to
        * BLOCK_FIELD_IS_OBJECT (3),证实: objective-c 对象11
        * BLOCK_FIELD_IS_BLOCK (7), 证实: 内部引用一个block
        * BLOCK_FIELD_IS_BYREF (8), 证实: 被__block修饰的变量
        * BLOCK_FIELD_IS_WEAK (16)  被__weak修饰的变量,只能被辅助copy函数使用
        * BLOCK_BYREF_CALLER (128)  block辅助函数调用(告诉内部实现不要进行retain或者copy)
     
     所以Block复制/处理助手只能生成3,7,8和24的四个标志值。
     
     __block复制/处理助手将分别为对象和块生成3或7的标志值,BLOCK_FIELD_IS_WEAK(16)或适当的时候会为128或“in”,为以下几种可能性:
        __block id                   128+3
        __weak block id              128+3+16
        __block (^Block)             128+7
        __weak __block (^Block)      128+7+16
    */
    // 参数
    /// destAddr: var地址
    /// object: var对象
    /// flags: var类型
    void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    
        // 引用的object被__block修饰
        if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
            // 将引用的object从栈复制到堆
            // flags将指示它是否包含__weak引用,并且需要一个特殊的isa(只有在GC)
            _Block_byref_assign_copy(destAddr, object, flags);
        }
        // 引用的object是一个block
        else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
            // 将引用的object从栈复制到堆, 如果此object已经在堆中会retain引用计数, 不会在次copy
            _Block_assign(_Block_copy_internal(object, flags), destAddr);
        }
        // 引用的object是一个objective-c 对象
        else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
            _Block_retain_object(object); // 当我们没有开启arc时,这个函数会retian此object
            _Block_assign((void *)object, destAddr); // 而此函数仅仅是赋值
        }
    }

    可以看出内部有3个判断, 分别调用了 _Block_byref_assign_copy 、 _Block_assign、_Block_retain_object

    1.引用的object被__block修饰

    // 运行时入口点,用于维护分支数据块的共享知识. 复制闭包后, 修改引用var时共享同步byref数据, byref 指针已经复制到堆上, 栈上修改堆上需要同步
    // var byref指针是否已经被复制到堆中,如果是的话,之后在用var直接retain引用计数。
    // 否则,我们需要复制它, 并更新栈forwarding指针指向
    // 参数
    /// dest: var地址
    /// arg: var对象
    /// flags: var类型
    static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
        struct Block_byref **destp = (struct Block_byref **)dest;
        struct Block_byref *src = (struct Block_byref *)arg;
        // 可以看到初始化时将flags赋值为0 代表初次copy var
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // 是否是weak(只有在GC) 所以可以忽略 === fasle
            bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
            /// 如果它的弱请求一个对象(仅在GC下重要)
            // 堆中申请一个src大小的内存并且用byref类型指针指向, 其实就是复制var到堆中
            struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
            // 下面就是为copy出来的byref 指针赋值而已
            // 非GC用于调用者,一个用于栈, 植入BLOCK_NEEDS_FREE, 保证后期在次引用不会再copy
            copy->flags = src->flags | _Byref_flag_initial_value;
            /// 同步数据用
            copy->forwarding = copy; // patch 拷贝堆中指针 指向自己
            src->forwarding = copy;  // patch 栈指向堆拷贝
            copy->size = src->size;
            // 如果是weak(只有在GC) 所以不会进
            if (isWeak) {
                copy->isa = &_NSConcreteWeakBlockVariable;  // 标记isa字段,因此它会弱扫描
            }
            // BLOCK_HAS_COPY_DISPOSE 这个byref var具有自己的copy/dispose辅助函数,而此时我们的内部实现不会进行默认的复制操作
            if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
                copy->byref_keep = src->byref_keep;
                copy->byref_destroy = src->byref_destroy;
                (*src->byref_keep)(copy, src);
            }
            else {
                // just bits.  Blast 'em using _Block_memmove in case they're __strong
                // 只是bits。Blast'em使用_Block_memmove,以防它们是__strong
                _Block_memmove(
                               (void *)&copy->byref_keep,
                               (void *)&src->byref_keep,
                               src->size - sizeof(struct Block_byref_header));
            }
        }
        // already copied to heap
        // 已复制到堆之后在调用
        else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
            // 当再次拷贝i时,则仅仅retain其引用计数
            latching_incr_int(&src->forwarding->flags);
        }
        // assign byref data block pointer into new Block
        //将byref数据块指针分配给新的Block
        _Block_assign(src->forwarding, (void **)destp); // 这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知
    }

    2. 引用的object是一个block

    static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
    static void _Block_assign_default(void *value, void **destptr) {
        *destptr = value;
    }

    3. 引用的object是一个objective-c 对象

    // 如果在MRC下内部会对其retain
    static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
    static void _Block_retain_object_default(const void *ptr) {
        if (!ptr) return;
    }
    /*
     * 系统首先会调用这个函数, 改变 _Block_retain_object 和_Block_release_object 的指向
     * 把_Block_retain_object 指向 retain如果引用对象可以做retain操作
     * 把_Block_release_object 指向 release如果引用对象销毁可以做release操作
     */
    void _Block_use_RR( void (*retain)(const void *),
                       void (*release)(const void *)) {
        _Block_retain_object = retain;
        _Block_release_object = release;
    }

    到此一个block复制完成, 接着我们看看block的销毁

    block销毁系统会首先调用如下方法

    // 我的猜想当一个block不在用到需要释放的时调用这个方法
    void _Block_release(void *arg) {
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        int32_t newCount;
        if (!aBlock) return;
        // release引用计数引用
        newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
        // 引用计数>0 不需要销毁
        if (newCount > 0) return;
        // BLOCK_NEEDS_FREE 这个标志表明block需要释放var
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // 代表这个block是有辅助参数的block
            if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
            // 销毁block
            _Block_deallocator(aBlock);
        }
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            ;
        }
        else {
            
        }
    }

    对其block做release操作

    // release引用计数
    static int latching_decr_int(int *where) {
        while (1) {
            int old_value = *(volatile int *)where;
            if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
                return BLOCK_REFCOUNT_MASK;
            }
            if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
                return 0;
            }
            if (OSAtomicCompareAndSwapInt(old_value, old_value-1, (volatile int *)where)) {
                return old_value-1;
            }
        }
    }

    可以看出 内部有这么一句判断 if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);

    当有辅助函数的时候系统会调用dispose, 前面我们看到block描述结构体初始化的时候, dispose初始化成 __stackTestBlock_block_dispose_0

    // 释放copy到堆上的Block对象
    // 命名规则: __FuncName_block_dispose_0
    ///        FuncName: 所在方法名
    static void __stackTestBlock_block_dispose_0(struct __stackTestBlock_block_impl_0*src) {
        // _Block_object_dispose其他文件封装的函数, 上面有函数声明引用, 当堆上的Block被废弃时, 释放Block截获的对象
        _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF: 表示对象是否进行retain或copy, 具体代表什么不详*/);
    }

    内部调用了_Block_object_dispose 去销毁内部引用对象

    // 当Blocks或Block_byrefs保存对象时,它们的destroy helper(销毁助手)通常会调用此入口点
    // 参数
    /// object: var指针
    /// flags:  var类型
    void _Block_object_dispose(const void *object, const int flags) {
        // 引用的object被__block修饰
        if (flags & BLOCK_FIELD_IS_BYREF)  {
            // 摆脱__block数据结构在一个block
            _Block_byref_release(object);
        }
        
        // 引用的变量是一个block
        else if ((flags & (BLOCK_FIELD_IS_BLOCK|BLOCK_BYREF_CALLER)) == BLOCK_FIELD_IS_BLOCK) {
            // 摆脱此block所持有的引用block
            _Block_destroy(object);
        }
        
        // 引用的变量是一个objective-c 对象
        else if ((flags & (BLOCK_FIELD_IS_WEAK|BLOCK_FIELD_IS_BLOCK|BLOCK_BYREF_CALLER)) == BLOCK_FIELD_IS_OBJECT) {
            // release引用对象
            _Block_release_object(object);
        }
    }

    内部也是有3个函数. 对应3种情况

    1. 引用的object被__block修饰

    // __block 修饰变量
    static void _Block_byref_release(const void *arg) {
        struct Block_byref *shared_struct = (struct Block_byref *)arg;
        int refcount;
        
        // 指向堆中的指针
        shared_struct = shared_struct->forwarding;
        
    
        // 栈或GC或全局参数
        if ((shared_struct->flags & BLOCK_NEEDS_FREE) == 0) {
            return; // stack or GC or global
        }
        refcount = shared_struct->flags & BLOCK_REFCOUNT_MASK;
        if (refcount <= 0) {
            
        }
        else if ((latching_decr_int(&shared_struct->flags) & BLOCK_REFCOUNT_MASK) == 0) {
            // 如果此指针支持辅助函数
            if (shared_struct->flags & BLOCK_HAS_COPY_DISPOSE) {
                (*shared_struct->byref_destroy)(shared_struct);
            }
            // 最后销毁当前对象
            _Block_deallocator((struct Block_layout *)shared_struct);
        }
    }

    2. 引用的变量是一个block

    // 释放一个复制的block, 使用的编译器在处理helpers中
    static void _Block_destroy(const void *arg) {
        struct Block_layout *aBlock;
        if (!arg) return;
        aBlock = (struct Block_layout *)arg;
        // 首先销毁当前block
        _Block_release(aBlock);
    }

    3. 引用的变量是一个objective-c 对象

    static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
    // MRC下会对其object做release
    static void _Block_release_object_default(const void *ptr) {
        if (!ptr) return;
    }
    /*
     * 系统首先会调用这个函数, 
     * 把_Block_retain_object 指向 retain如果引用对象可以做retain操作
     * 把_Block_release_object 指向 release如果引用对象销毁可以做release操作
     */
    void _Block_use_RR( void (*retain)(const void *),
                       void (*release)(const void *)) {
        _Block_retain_object = retain;
        _Block_release_object = release;
    }

    把对象都销毁就此一个block真正的销毁

    参考博文

    谈Objective-C Block的实现

    Block_private.h

    Block技巧与底层解析

    gitHub的源码runtime.c 

  • 相关阅读:
    callAfter 例子2
    wxpython
    python 处理excel 进程无法退出的问题
    常用电子技术网
    Emeditor 与正则表达式
    Windows程序中的字符编码
    Delphi7 无法启动 问题搞定
    2007中国发烧盘点之作《天路》发烧女声版
    一台电脑安装多个(虚拟)网卡问题
    无法删除xxx文档/文件夹:找不到指定的路径。请确定指定的路径是否正确
  • 原文地址:https://www.cnblogs.com/MrTao/p/6824967.html
Copyright © 2020-2023  润新知