zoukankan      html  css  js  c++  java
  • iOS OC语言: Block底层实现原理

    先来简单介绍一下Block
    Block是什么?苹果推荐的类型,效率高,在运行中保存代码。用来封装和保存代码,有点像函数,Block可以在任何时候执行。

    Block和函数的相似性:(1)可以保存代码(2)有返回值(3)有形参(4)调用方式一样。

    Block 底层实现

    定义一个简单的block


     

    我们再给a赋值为20,此时打印出来a 的值还是10


     


    但当我们在第一次给a 赋值时,前面加上__block 的时候,则打印出来20。


     


    那么为什么加上__block 后 就打印出20了呢,这个原理是什么呢?

    其实可以用两个词来概括:传值 和传址。 可能这样说大家觉得有点扯,接下来 用C++ 代码进行编译。
    打开终端做如下操作 在当前文件夹下会得到一个.cpp 文件。


     

     

    此时打开当前的.cpp 文件(会有差不多10万行代码),前面我们都忽略,只需要滚动到最后,此时你会发现block跟OC中的变化。


     

    接下来我们一个个来看这个block,先来看等号左边的。

     void(*block)()

    这是一个没有参数没有返回值的函数指针,既然是一个函数指针,那它就是一个变量,变量里面只能保存函数地址,然后它又在等号的左边是不是意味着右边返回的是一个函数地址(自己推断)。

    再看等号右边:

    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    • 参数(自我推断):

      • ((void (*)()) 强转(自己理解其实没有实际含义,不影响自己本身的类型)
      • & 取址 后面都是函数的调用,如果不是也不会得到一个函数指针的。
      • __main_block_impl_0 这是一个函数名,这个函数有三个参数, com+F 搜索一下,又会发现这是一个结构体,结构体如下:

          struct __main_block_impl_0 {
              struct __block_impl impl;
              struct __main_block_desc_0* Desc;
              int a;

        可能你会疑惑,刚刚说这是一个函数,而现在是一个结构体。其实在 c++ 里面结构体相当于OC的类,c++ 里面结构体拥有自己的属性以及构造方法和方法。那么为什么取一个结构体的地址呢? 其实它取得是下面这段代码的地址:

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

        那么在上面个方法实现里,又有四个参数。而在刚刚调用的时候只有三个参数,多了一个参数 flags= 0,这个参数其实就相当于Swift中指定了一个默认值,不传也有值,可以忽略。那么后面继续:

      • a(_a) : 在 c++ 里面 指定_a(形参) 将来赋值给a 这个实参,也就是这个__main_block_impl_0 结构体中的 int a;在这里 int a = 10;
      • impl.FuncPtr = fp; 将fp赋值给了 impl 结构体的 FuncPtr 参数, 在这个参数里面存放的是下面这段代码的地址:

        static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            int a = __cself->a; // 这里 int a = 10;
            printf("%d\\\\n",a); // 打印出a
        }
      • __main_block_desc_0_DATA com+ F 搜索 定义的就是与大小相关的信息,代码如下:
        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)};
      • a 直接放a 其实就相当于把a 当前的值拿过来,如果是&a, 就是a的地址。请看下图:

     

    接下来,又重新给 a赋值为 20,但是Block 最终要找到 FuncPtr 里面存放的是值来执行, 在这里才会最终执行打印a 的值的代码,但是这段代码里 a 是 10 了。所以最终打印的还是10。


     
    最后可以概括为block 底层实现 分两种:刚刚上面的就是第一种(不加__block), 会创建一个结构体,实现构造方法,来接收三个参数。

    接下来看加上__block 的实现。
    修改我们的代码:


     


    再次在终端里面进行编译,你会发现生成的结构体会变化。


     

    等号左边会封装一个__Block_byref_a_0 结构体类型的变量a,下面是结构体的声明:

      truct __Block_byref_a_0 {
        void *__isa;   //isa 类型的指针 自己的类型
        __Block_byref_a_0 *__forwarding;  //与自己结构体同名,是一个自己类型的结构体的指针,存放的是自己的地址
        int __flags;  // 标记
        int __size;  // 类型的大小
        int a;  // a 属性 保存变量的值
      };

    等号右边:

      {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    • 参数:
      • (void*)0 : 一个指针直接存到isa里面
      • (__Block_byref_a_0 *)&a: 强转 存放的是自己的地址
      • 0 : 会传给 flags
      • sizeof(__Block_byref_a_0), 10: 类型的大小
      • 10: a 的值, 仅仅是创建。

    这里仅仅是创建,因为使用了__block 所以创建了一个block 类型的结构体,接下来会才是调用block,你会发现其余参数和第一种实现都一样,唯一不同的是再去取值的时候,拿到的是结构体的地址,只要把地址传递过去,就有了最高的操作权限,到时候再去取值就可以取到内存中最新的值。


     

    接下来(a.__forwarding->a) = 20; 这句代码是拿到结构体里面的地址去修改a的值为20。

    后面再去打印,打印的就是内存地址中最新的值,所以就是20。

    非常感谢大家阅读完这篇文字,如有什么需要补充的,欢迎提出



    文/Liwjing(简书作者)
    原文链接:http://www.jianshu.com/p/e23078c11518
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    springboot springcloud zuul 过滤器
    springboot springcloud eureka 熔断器
    javaweb servlet filter
    maven nexus 搭建私服(二)
    springboot springcloud zuul 网关入门
    springboot springcloud 配置中心
    springboot springcloud eureka 入门
    java rabbitmq
    java jvm调优
    maven nexus 搭建私服(一)
  • 原文地址:https://www.cnblogs.com/machao/p/5484755.html
Copyright © 2011-2022 走看看