美文网首页
iOS Block 部分一

iOS Block 部分一

作者: 飞不越疯人院 | 来源:发表于2020-06-30 15:52 被阅读0次

    主要讲解 Block 的底层实现原理;

    Block部分一
    Block部分二
    Block部分三
    Block知识点总结

    基础知识 C++中结构体的构造函数是怎么实现的?

    很多 OC 对象低层都转化为了C++的结构体, 由于不懂 C++的语法, 所以网上查了下一些C++结构体的基础知识;
    结构体的构造函数

     typedef struct Test{
        int id;
        string name;
         // 用以不初始化就构造结构体
        Test(){} ;
        //只初始化name
        Test(string _name) {
          name = _name;
        }
        /*
        同时初始化id,name , 把入参的 id 和 name 为结构体中的 id 和 name 赋值;
        参考下方 OC 类的构造函数
        */
        Test(inr _id,string _name): id(_id),name(_name)}{};
       }; 
    

    类似于我们的构造函数实现;

    ///.h 文件声明
    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface Person : NSObject
    
    - (instancetype)initWithName:(NSString *)name
                             age:(NSInteger)age;
    @end
    NS_ASSUME_NONNULL_END
    ///.m文件实现
    #import "Person.h"
    
    @interface Person ()
    @property (nonatomic, strong) NSString  *name;
    @property (nonatomic, assign) NSInteger age;
    @end
    
    @implementation Person
    - (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
        self = [super init];
        if (self) {
            self.name = name;
            self.age = age;
        }
        return  self;
    }
    @end
    

    1. 什么是Block?

    Block是一个对象, 对象中封装了一个函数以及函数执行的上下文;Block的调用本质是函数的调用;


    2. Block的底层实现是什么?

    block 的底层是一个结构体__XXXX_block_impl_0

    测试代码如下, 一个最简单的Block; 通过指令转换为C++文件后得到如下结果;

    ///viewDidLoad中写一个最简单的block
    - (void)viewDidLoad {
        [super viewDidLoad];
        ///case1  block的底层结构
         ^{
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
          };
    }
    

    通过指令转化为C++文件, 底层实现为

    static void _I_ViewController1_viewDidLoad(ViewController1 * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController1"))}, sel_registerName("viewDidLoad"));
      /*
        Block的底层实现如下;  __XXXX_block_impl_0结构体即为block的底层结构
        通过__XXXX_block_impl_0构造函数来初始化这个结构体, 入参两个参数
        __XXXX_block_func_0: 实际block中需要执行的的代码块
        __XXXXX_block_desc_0_DATA: block 的一些信息
      */    
    ((void (*)())&__ViewController1__viewDidLoad_block_impl_0((void *)__ViewController1__viewDidLoad_block_func_0, &__ViewController1__viewDidLoad_block_desc_0_DATA));
    }
    ===>
    block 的底层为一个结构体__XXXX_block_impl_0;
    ///__XXXX_block_impl_0的结构如下
    struct __ViewController1__viewDidLoad_block_impl_0 {
      ///bock 的实现信息
      struct __block_impl impl;
      ///block 的描述信息
      struct __ViewController1__viewDidLoad_block_desc_0* Desc;
      __ViewController1__viewDidLoad_block_impl_0(void *fp, struct 
      /// block 的构造函数, 为结构体里面的变量赋值, 参考 OC 类的构造函数
    __ViewController1__viewDidLoad_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        ///block中需要执行的代码块, 具体放在内存中的代码段;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    ===>
    ///实际block中的代码块(这个 block 中就是那4个 NSLog)
    static void __ViewController1__viewDidLoad_block_func_0(struct __ViewController1__viewDidLoad_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_0);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_3);
    }
    
    ===>
    ///block 的实现信息
    struct __block_impl {
      ///isa 指针
      void *isa;
      ///标志; 通过构造函数可以看到传入的是0; 
      int Flags;
      ///保留字段
      int Reserved;
      /*
        block 中需要执行的代码块封装的函数地址;
       __XXXX_block_impl_0结构体的构造函数会为其赋值
    */
      void *FuncPtr;
    };
    
    ===>
    ///block的一些描述信息
    static struct __ViewController1__viewDidLoad_block_desc_0 {
      ///保留字段
      size_t reserved;
      ///block 所占的空间(看下面对sizeof(struct __main_block_impl_0)即可得知)
      size_t Block_size;
    } __ViewController1__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController1__viewDidLoad_block_impl_0)};
    

    总结底层的几个结构体和函数
    XXXX_block_impl_0 : 就是block对象的底层结构体;
    __block_impl: 就是封装了block具体实现的结构体;
    XXXX_block_func_0: 就是封装了block内部执行代码块的函数;
    XXXX_block_desc_0: 就是封装了block的一些基本信息;

    注意: 如果访问了auto变量(ARC), 因为对block进行了copy到堆区的操作, 所以通过clang后会出现两套block, 而调用部分会变成 XXXX_block_impl_1, XXXX_block_func_1等, 因为这个是对XXXX_block_impl_0, XXXX_block_func_1的拷贝后的;


    3. 如何定义Block并且通过名字调用的?

    在弄清这个问题首先印证一个问题;一个结构体嵌套结构体时, 如果第一个变量是结构体, 则可以通过内存地址直接访问内层结构体中的变量;代码如下:

    #import "ViewController2.h"
    @interface ViewController2 ()
    @end
    ///case2的印证结构体嵌套时使用
    struct SubStruct {
        int a;
    };
    struct SuperStruct {
        struct SubStruct  subs;
        int b;
    };
    @implementation ViewController2
    - (void)viewDidLoad {
        [super viewDidLoad];
        struct SuperStruct supers = {{10}, 2};
        NSLog(@"%p    %p", &(supers.subs), &supers);
        ///(强制转换为int值)(强制转换为SubStruct)&取supers的地址->访问SubStruct中变量
        int subStruct_a = (int)((struct SubStruct *)&supers)->a;
        NSLog(@"通过外层结构体地址强行访问内存结构体的变量 %d", subStruct_a);
     }
    

    两个地址是一样的; 由于substructsuperstruct 的第一个变量, 所以substruct和外层的superstruct地址是一样的;

    2020-06-24 17:19:15.005827+0800 BlockMore1[6213:90176] 0x7ffee413cc88    0x7ffee413cc88
    

    可以通过内存地址方式superstruct调用内层substruct变量;

    2020-06-24 17:19:15.005918+0800 BlockMore1[6213:90176] 通过外层结构体地址强行访问内存结构体的变量 10
    

    通过下面代码来探究block的调用方式;

        ///case2  定义一个 block 然后调用
        void(^Case2Block)(void) =  ^{
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
            NSLog(@"Block内部内容;");
        };
        Case2Block();
    

    通过指令转换为C++文件后的代码为

          ///Case2Block的定义过程
         void(*Case2Block)(void) = ((void (*)())&__ViewController2__viewDidLoad_block_impl_0((void *)__ViewController2__viewDidLoad_block_func_0, &__ViewController2__viewDidLoad_block_desc_0_DATA));
          ///Case2Block的实际调用
         ((void (*)(__block_impl *))((__block_impl *)Case2Block)->FuncPtr)((__block_impl *)Case2Block);
    

    直接看不够直观; 我们将一些强制转换的相关代码去掉;

    • 定义部分
      强制转换相关的代码部分去掉后,定义block过程伪代码大致如下, 调用__XXXX_block_impl_0的构造函数, 两个入参
      __XXXX_block_func_0: 实际block中需要执行的代码块,
      &__XXXX_block_desc_0_DATA: 这个 block一些信息;
      实际第三个参数flags, 由于构造函数中直接赋值 flags=0(case1的讲解), 所以这里不用传入;
      最后的结果就是讲生成的结构体的通过&取地址然后赋值给Case2Block指针;
      简化后为: void(*Case2Block) = &__XXXX_block_impl_0(__XXXX_block_func_0,&__XXXX_block_desc_0_DATA)
    • 调用部分
      强制转换相关的代码部分去掉后,调用block过程伪代码大致如下
      Case2Block的地址就是__XXXX_block_impl_0地址, 通过上面结构体嵌套的论证, 我们可以知道
      这种方式最后调用的是__block_impl结构体中的FuncPtr(就是实际Case2Block中的需要执行的代码块);
      简化后为: Case2Block->FuncPtr(Case2Block)

    4. 什么是 Block 的变量捕获(capture)?

    首先看下方代码, 我们都知道最后的 打印结果为 a = 100, b = 200, c = 3, d = 400; 原因是 a是全局变量, b是全局静态变量, c是局部变量, d是静态局部变量; 但是为什么会这样? 我们从源码角度分析下为什么;

    
    #import "ViewController3.h"
    @interface ViewController3 ()
    @end
    
    int a = 1;//全局变量, 整个工程内都有效;
    static int b = 2;//静态全局变量, 只在定义它的文件内有效;
    @implementation ViewController3
    - (void)viewDidLoad {
        [super viewDidLoad];
        /******************************************************************/
        ///case3   block 的变量捕获
        int c = 3;//局部变量, 只在定义他的函数内有效;1
        static int d = 4;//静态局部变量, 只在定义他的函数内有效, 且内存只分配一次;
        /*
        block将局部变量捕获到block内部, 主要一个是捕获值, 一个是捕获址;全局变量不捕获;
        */
        void(^Case3Block)(void) =  ^{
            NSLog(@"a = %d, b = %d, c = %d, d = %d", a, b, c, d);
        };
        a = 100;
        b = 200;
        c = 300;
        d = 400;
        Case3Block();
    }
    @end
    
    2020-06-25 09:50:45.294219+0800 BlockMore1[2775:24440] a = 100, b = 200, c = 3, d = 400
    

    将上方的代码通过指令编译为C++文件后, 我们可以看到Case3Block的相关代码如下

    /*
    注意: 全局变量a和b底层实现也是如此, 并没有发生变化;
    */
    int a = 1;
    static int b = 2;
    /*
    Case3Block的底层实现,已经变化, block内部重新定义两个变量, 一个全新的c变量, 和一个地址变量d; 
    */
    struct __ViewController3__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController3__viewDidLoad_block_desc_0* Desc;
      int c;
      int *d;
      __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    

    看过上方源码后相信就可以很直观的得知:
    全局变量, block内部并不会捕获创建新的变量进行存储, 所以更改全局变量后,仍然是直接调用全局变量;
    局部变量: block会进行变量捕获, 局部变量是捕获值, 静态局部变量捕获址;


    补充: self是全局变量还是局部变量?block内是否会捕获self?

    self是局部变量, block内部会捕获self;
    通过底层代码验证捕获self;

    @interface ViewController31 () {
        int  _age;
    }
    @property (nonatomic, assign) int  count;
    @end
    @implementation ViewController31
    - (void)viewDidLoad {
        [super viewDidLoad];
        ///不论是访问成员变量还是访问属性, 最终都是会将self捕获到self内部;
        ^ {
            NSLog(@"age = %d,  count = %d", _age, self.count);
        };
    
    static void _I_ViewController31_viewDidLoad(ViewController31 * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController31"))}, sel_registerName("viewDidLoad"));
        ///block的定义
    ((void (*)())&__ViewController31__viewDidLoad_block_impl_0((void *)__ViewController31__viewDidLoad_block_func_0, &__ViewController31__viewDidLoad_block_desc_0_DATA, self, 570425344));
    }
    
    ///block的实现
    struct __ViewController31__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController31__viewDidLoad_block_desc_0* Desc;
      ///新增变量*self, 用来存储self
      ViewController31 *self;
      __ViewController31__viewDidLoad_block_impl_0(void *fp, struct __ViewController31__viewDidLoad_block_desc_0 *desc, ViewController31 *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    通过底层代码验证self是局部变量;

    - (void)test31 {
        NSLog(@"实例方法self指向当前实例对象");
    }
    
    + (void)test32 {
        NSLog(@"类方法self指向当前类对象");
    }
    

    所有的实例方法中底层默认传入*self指向此实例对象; 所以可以得知self是局部变量; 类方法中调用self是指向当前类对象;但是无论是实例方法还是类方法转换为C++文件可以看到都是有入参self参数的;

     /*
    每个实例方法的底层, 是默认会入参两个参数
    *self  指向当前的实例对象
    _cmd 指向当前的方法
      */
    static void _I_ViewController31_test31(ViewController31 * self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_1);
    }
    
     /*
    每个类方法的底层, 是默认会入参两个参数
      self 指向当前的类对象
    _cmd 指向当前的方法
      */
    static void _C_ViewController31_test32(Class self, SEL _cmd) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_2);
    }
    

    通过指令将OC文件转换为C++文件
    指令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件.m -o 文件-arm64.cpp
    如果需要链接其他框架, 使用-framework参数; 例:-framework UIKit


    参考文章和下载链接
    测试代码
    iOS clang指令报错问题总结
    Apple 一些源码的下载地址
    C++中结构体的构造函数
    全局变量、静态全局变量、静态局部变量和普通局部变量的区别

    相关文章

      网友评论

          本文标题:iOS Block 部分一

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