美文网首页
Objective-C Block Part2 - 实现原理

Objective-C Block Part2 - 实现原理

作者: f939f3106e72 | 来源:发表于2017-03-30 11:38 被阅读41次

    Block 的本质

    Objective-C Block Part1 中总结了 Block 的一些使用规则,但是为什么要遵循这些规则,还有这些规则是怎么来的? 这就需要探寻 Block 的本质,明白它的实现原理。

    注: 我们通过 clang 把包含 Block 的 Objective-C 的代码转换成 C++ 实现代码,以此来分析 Block 的实现原理。通过 clang 重写的代码仅供我们分析和参考,在极少数地方和实际运行时有细微出入。

    简单 Block 转换后的代码分析

    下面通过 clang -rewrite-objc main.m 把一个简单的 Block 转换成 C++ 代码 main.cpp :

    /** 转换前的 Objective-C 代码: */
    int main(int argc, const char * argv[]) {
        char *name = "Steve Jobs";
        ^() {
            printf("hello %s :)", name);
        }();
        return 0;
    }
    
    /** 转换后裁剪出的重要代码: */
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      char *name;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_name, int flags=0) : name(_name) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        char *name = __cself->name; // bound by copy
        printf("hello %s :)", name);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main(int argc, const char * argv[]) {
        char *name = "Steve Jobs";
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, name))();
        return 0;
    }
    

    我们看到 Block 的本质其实就是一个名为 __main_block_impl_0 的结构体。这个结构体包含的主要内容:

    1. __block_impl 结构体的变量。 (block 结构体的第一个成员都是 __block_impl, 所以 __block_impl 是 block 的基础结构体。
    2. __main_block_desc_0 结构体的变量。( __main_block_desc_0 是描述 block 的结构体。
    3. 可选的 被截获的成员,例如本例的 char *name ,这一块下一节细述。
    4. __main_block_impl_0 结构体的构造函数

    再来分析一下 __main_block_impl_0 结构体的第一个成员 __block_impl 结构体里的内容:

    1. void *isa; (所有的 OC 对象的都有 isa指针,这也说明 Block 是一个 OC 对象。
    2. int Flags; (用于按 bit 位表示一些 block 的附加信息。
    3. int Reserved; (保留变量。
    4. void *FuncPtr; (block 执行的函数指针,这个函数指针包含的是 OC 里面写在 Block 里面的代码。

    __main_block_desc_0 的内容:

    1. size_t reserved; (保留变量
    2. size_t Block_size; (保存 Block 的大小

    Block 截获变量

    上面一节大概了解了 Block 是个什么东西,这一节则会进行更深入的探索,搞清楚 Block 对各种类型的变量在内部是如何处理。下面通过 clang 转换一个包含各种变量的 Block 来分析这些问题:

    /** 转换前的 Objective-C 代码(ARC): */
    typedef void(^blk_t)();
    
    static int static_global_val = 1;       // 静态全局变量(C
    static NSObject *static_global_obj;     // 静态全局变量(OC
    int global_val = 1;                     // 全局变量(C
    NSObject *global_obj;                   // 全局变量(OC
    
    int main(int argc, const char * argv[]) {
        int automatic_val = 1;                                   // 自动变量(C
        NSObject *automatic_obj = [NSObject new];               // 自动变量(OC
        
        __block int __block_val = 1;                             // __block变量(C
        __block NSObject *__block_obj = [NSObject new];         // __block变量(OC
        
        static int static_val = 1;                               // 静态变量(C
        static NSObject *static_obj;                             // 静态变量(OC
        
        static_global_obj = [NSObject new];
        global_obj = [NSObject new];
        static_obj = [NSObject new];
        
        blk_t block = ^{
          static_global_val = 1;
          static_global_obj = [NSArray array];
          global_val = 1;
          global_obj = [NSArray array];  
          static_val = 1;
          static_obj = [NSArray array];
            
          __block_val = 1;
          __block_obj = [NSArray array];
          printf("%d,%p", automatic_val, automatic_obj);
          //automatic_val = 1;              // 报错
          //automatic_obj = [NSArray array]; // 报错
        };
        block();
    }
    

    下面是转换成 C++ 的代码, 其中裁剪出重要的内容来显示,代码后面是分析上面问题的答案:

    /** 转换后的C++代码(ARC): */
    typedef void(*blk_t)();
    
    static int static_global_val = 1;
    static NSObject *static_global_obj;
    int global_val = 1;
    NSObject *global_obj;
    
    struct __Block_byref___block_val_0 {
      void *__isa;
      __Block_byref___block_val_0 *__forwarding;
      int __flags;
      int __size;
      int __block_val;
    };
    struct __Block_byref___block_obj_1 {
      void *__isa;
      __Block_byref___block_obj_1 *__forwarding;
      int __flags;
      int __size;
      void (*__Block_byref_id_object_copy)(void*, void*);
      void (*__Block_byref_id_object_dispose)(void*);
      NSObject *__block_obj;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *static_val;
      NSObject **static_obj;
      int automatic_val;
      NSObject *automatic_obj;
      __Block_byref___block_val_0 *__block_val; // by ref
      __Block_byref___block_obj_1 *__block_obj; // by ref
      
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, NSObject **_static_obj, int _automatic_val, NSObject *_automatic_obj, __Block_byref___block_val_0 *___block_val, __Block_byref___block_obj_1 *___block_obj, int flags=0) : static_val(_static_val), static_obj(_static_obj), automatic_val(_automatic_val), automatic_obj(_automatic_obj), __block_val(___block_val->__forwarding), __block_obj(___block_obj->__forwarding) { 
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    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};
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref___block_val_0 *__block_val = __cself->__block_val; // bound by ref
      __Block_byref___block_obj_1 *__block_obj = __cself->__block_obj; // bound by ref
      int *static_val = __cself->static_val;       // bound by copy
      NSObject **static_obj = __cself->static_obj; // bound by copy
    
        static_global_val = 1;
        static_global_obj = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
        global_val = 1;
        global_obj = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
    
        (*static_val) = 1;
        (*static_obj) = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
        (__block_val->__forwarding->__block_val) = 1;
        (__block_obj->__forwarding->__block_obj) = objc_msgSend(objc_getClass("NSArray"), sel_registerName("new"));
    }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src{...}
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {...}
    
    int main(int argc, const char * argv[]) {
        int automatic_val = 1;
        NSObject *automatic_obj = objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new"));
      
        __Block_byref___block_val_0 __block_val = {(void*)0,&__block_val,0,sizeof(__Block_byref___block_val_0),1};
        __Block_byref___block_obj_1 __block_obj = {(void*)0, &__block_obj, 33554432, sizeof(__Block_byref___block_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"))};
    
        static int static_val = 1;
        static NSObject *static_obj;
    
        static_global_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
        global_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
        static_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
        
        blk_t block = ((void (*)())&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_val, &static_obj, automatic_val, automatic_obj, &__block_val, &__block_obj, 570425344));
      
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    

    1. Block 怎么处理自动变量?

    从上面的 C++ 代码中可以看出,在 Block 对应的 __main_block_impl_0 struct 中有和自动变量 automatic_val automatic_obj 相同的类型的结构体成员。 __main_block_impl_0 struct 的构造方法 __main_block_impl_0 的参数中也会接受 automatic_val automatic_obj 同类型的参数,然后把其赋值给对应的 strcut 成员。 可以看下在 main 函数中对 __main_block_impl_0 构造函数的调用。 这里有几个值得注意的点:

    1. 对于 C 基础类型 automatic_val 直接传递的是值 1 , 所以 mian 函数中定义的 int automatic_val 和 Block 对应结构体中的 automatic_val 结构体成员不是同一个变量。通过下面的小例子可以证明:

      int main(int argc, const char * argv[]) {
        int automatic_val = 1;
        blk_t block = ^ { printf("inside  in room val:%d \n",automatic_val);};
        automatic_val = 2;
        printf("outside in room val:%d \n",automatic_val);
        
        block();
      }
      

      下面是上面代码的输出,可以看到在 Block 外改变 val 的值根本影响不到 Block 内的值,因为他们不在同一块内存上

      outside in room val:2 
      inside  in room val:1 
      
    2. 对于 OC 对象类型 automatic_obj 是直接传递指针,和 automatic_val 同理,如果在 Block 外新建一个 OC 对象类型 的指针,再赋值给 automatic_obj 变量也对 Block 内的 automatic_obj 结构体成员是没有影响的,因为这两个变量里面此时装的已经是不同的指针了。 但是当它们装的是同一个指针时,是可以通过调用对象的方法来相互影响的, 举个栗子,在 Block 外更改可变数组里的内容是会影响到 Block 内部的可变数组的,因为此时这两个变量是装的同一个指针:

      int main(int argc, const char * argv[]) {
          NSMutableArray *automatic_obj = [NSMutableArray arrayWithObjects:@"1", @"2", nil];
          blk_t block = ^ {
              NSLog(@"inside  in room obj: %@", automatic_obj);
          };
          [automatic_obj addObject:@"3"];
          NSLog(@"outside in room obj: %@", automatic_obj);
        
          block();
      }
      
    3. 在 Block 对应的 __main_block_impl_0 struct 中,修饰 automatic_obj 的是 strong 所有权修饰符,所以 Block 会对这个对象进行持有, 为什么系统这么去设计让 Block 持有自动变量? 是因为 Block 能够超出其所在的函数作用域存在,而 OC 对象类型 的自动变量在超出函数作用域时就会被释放被释放,Block 此时执行时这个对象已经被销毁了。为了避免这种情况 Block 持有了 OC 对象类型 的自动变量。

    2. Block 怎么处理 静态全局变量 / 全局变量 / 静态变量 ?

    之所以把这三种变量归纳到一起,是因为它们的生命周期在一个程序中会一直存在。 Block 中使用 静态全局变量全局变量 时,因为它们的作用域是全局的,并且是在程序中一直存在的,所以转换后这部分没有任何变化可以直接使用,并且可以在 Block 中重新赋值。 Block 中使用 静态变量 转换后会在 Block 对应的 __main_block_impl_0 struct 中追加这个 静态变量 的类型的指针,然后通过 strcut 的构造函数传递进去,通过这种方式扩大了这个静态变量可访问的作用域, 使其可以在 __main_block_func_0 函数中访问。同时也因为传递的是 静态变量 类型的指针所以具备了重新赋值的能力。

    3. Block 怎么处理 __block 修饰的自动变量 ?

    上面的例子中 __block 修饰的自动变量 __block_val __block_obj 在转换后分别变成了 __Block_byref___block_val_0__Block_byref___block_val_1 结构体,并且它们第一个成员都是 isa 指针,这说明它们都是 OC 对象。第二个成员是 __forwarding 指针 目前是指向这个结构体的本身。 这两个结构体分别还包含着对应的 __block 自动变量类型的 结构体成员。

    Block 的储存域

    因为 Block 对应的结构体第一个成员是 isa 指针 ,所以 Block 也是个 OC 对象 ,那么 isa 指针 指向的就是 Block 的类了。以前我们接触到的都是 _NSConcreteStackBlock ,其实还有另外两种: _NSConcreteGlobalBlock _NSConcreteMallocBlock 。 这一节就是来探讨这三种 Block 的不同和作用。

    三种 Block 在内存的存储区域

    • _NSConcreteStackBlock 类的 Block 对象是设置栈区上的,超出其所在的函数作用域就会被释放。
    • _NSConcreteGlobalBlock 类的 Block 对象是设置在 .data 区上,在程序运行时永久存在的。
    • _NSConcreteMallocBlock 类的 Block 对象是设置在堆区上。

    怎么区分这三种 Block

    _NSConcreteGlobalBlock

    • 在所有方法外定义的 Block 为 Global Block
    • 当 Block 中没有截获自动变量是为 Global Block

    _NSConcreteMallocBlock

    Malloc BlockStack Block 被执行 copy 操作后得到的。它能让 Block 超出函数/方法的作用域而存在。

    _NSConcreteStackBlock

    除了上面的情况,剩下的就都是 Stack Block 了。当其所在的函数作用域结束时,这个 Block 就会被回收。

    那些情况系统会自动帮你调用 copy 方法

    通过 copy 操作可以让 Stack Block 拷贝成 Malloc Block,但是在一些情况下,系统会自动的帮我们执行 copy 操作:

    • ARC Block 作为函数返回值返回时会自动对 Block 执行 copy 操作。
    • ARC 下将 Block 赋值给附有 __strong 修饰符的变量时。 所以在 ARC 下不用调用 copy 操作,直接把它赋值给 __strong 变量就可以达到效果,还有对于 Block 类型的 @property 的 attribute 不用写 copy。直接使用默认的 __strong ,有太多人在 ARC 下还是用 copy 去修饰。 完全没有必要
    • 在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递 Block 时。

    Block copy 时会发生什么? 会造成什么影响?

    大部分的 Block 对象是设置在栈内存上的,为了使 Block 能够超出其函数作用域的范围。可以使用 copy 操作将其从栈内存拷贝到堆内存中。对于 Block 中的 __block 变量一开始它也是配置在栈内存中的,在超出函数作用域时它也会被释放。所以在拷贝 Block 对象到堆内存中时,也会同时拷贝这个 Block 使用的 __block 变量到堆内存中。 当多个 Block 对象同时使用一个 __block 变量时,如果其中有个 Block 已经把 __block 变量拷贝到堆内存上了。后面的 Block 再次对这个 __block 变量执行 copy 操作时只会增加这个 __block 变量的持有。 等 Block 销毁时就会减少 __block 变量的持有。当没有 Block 持有 __block 变量时它就会被回收。这和我们一直使用的引用计数的内存管理方式相同。

    为什么要设计 __forwarding 这个东西?

    上一节的例子中 __block_val __block_obj 这两个 __block 变量在被转换后,分别变成了 __Block_byref___block_val_0 __Block_byref___block_val_1 结构体,并且结构体的第二个成员都是 __forwarding 指针。 这个 __forwarding 指针目前是指向自己的。为什么要去设计 __forwarding 指针这个东西?这需要刚才讨论的 Block 对象的拷贝结合在一起看。

    __forwarding 指针存在的意义是不管 __block 变量是配置在栈上还是堆上,都能够正确的访问变量。当 Block 对象被拷贝到堆内存中是, __block 变量也被拷贝到堆内存中。那么此时可以同时访问栈上的 __block 变量 和 堆上的 __block 变量。他们都是通过下面的方式访问的, 都是通过 __forarding 指针:

    __block_val->__forwarding->__block_val
    __block_obj->__forwarding->__block_obj
    

    栈上的 __block 在被拷贝到堆内存时,会改变栈内存的 __forwarding 指针,让其指向堆内存的 __block 变量。 所以通过这个设计让 访问的 __block 变量无论在 Block 中还是 Block外,__block变量是在堆内存还是栈内存上,访问的都是同一个 __block 变量。

    为什么 Block 中的静态变量可以修改, 而自动变量不能修改?

    上面讲到 Block 对应的结构体因为保存的是 静态变量 的类型的指针,所以 静态变量 可以在 Block 中被重新赋值。那自动变量为什么不也设计成这样,使其拥有在 Block 中被重新赋值的能力呢? 这是因为自动变量的超出其所在的函数作用域时就会被销毁掉。但是 Block 又可以超出其自身作用域而存在。如果像对待 静态变量 那样去对待 自动变量 ,很可能出现的情况就是当 Block 去操作/访问 自动变量 时。自动变量已经被销毁。

    小测验

    这个关于 Block 的小测试 这是在唐巧的 blog 《谈Objective-C block的实现》 中发现的, 觉得很能考察对 Block 的理解程度,做到全对感觉对 Block 了解就很清楚了。

    参考资源

    Objective-C 高级编程

    相关文章

      网友评论

          本文标题:Objective-C Block Part2 - 实现原理

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