美文网首页
[iOS]Block本质的一些研究

[iOS]Block本质的一些研究

作者: 未来行者 | 来源:发表于2018-08-27 19:23 被阅读48次

一些安排和感想

从上一篇的ARC的实现细节开始,会陆续去研究一些老生常谈的话题,诸如:Block,Runtime,GCD,Runloop,Animation等知识点,并生成一些文章.目的在于巩固已有知识,同时希望能帮到一些"有缘人".

从一段代码开始

先看一段代码:

int main(){
    void (^block)(void) = ^{
        printf("block");
    };
    block();
    return 0;
}

这段代码的作用很简单:在block内进行简单的打印.但这个过程中到底发生了什么呢?在Terminal我们输入clang -rewrite-objc + 这个文件,我们可以得到如下的C++源码(过滤一些代码后):

// block 的结构体
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr; // 方法指针
};
// 当前第一个block的实现
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 构造函数用来接收外面传入的实际实现的方法指针等信息 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; // block类型
    impl.Flags = flags;
    impl.FuncPtr = fp; // 实际实现的方法指针
    Desc = desc;
  }
};
// 这个函数就是最终执行的函数:打印内容
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("block");
    }
// 描述信息:block大小,版本等
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(){
    // 生成一个block变量,这个变量是通过__main_block_impl_0内的构造方法生成的
    // 传入了实际方法实现__main_block_func_0的地址,和描述信息__main_block_desc_0_DATA的地址
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // 上一步之后__block_impl的FuncPtr指针已经指向了__main_block_func_0的地址
    // 因为__main_block_func_0方法需要传入__main_block_impl_0类型的变量,所以直接传入上面的block变量即可完成函数调用.
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

以上代码中注释了一些关键信息,充分说明了block本质是什么,总结一下:

第1步:首先通过构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)生成了一个__main_block_impl_0类型的变量block,block内的FuncPtr指针指向了__main_block_func_0的地址.
第2步:__main_block_func_0函数又有一个__main_block_impl_0类型的形参,所以这里实际调用如下:
(*block->impl.FuncPtr)(block)

Block也是一个对象

从上面的C++源码中我们不难发现这个结构体__block_impl:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

我们对比一下objc_class结构体class_t(在runtime源码的版本为437.1中的runtime/objc-runtime-new.h):

typedef struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    class_rw_t *data;
} class_t;

通过对比我们可以发现,一般对象和block都有一个isa指针成员变量,所以这里可以理解block是一个对象,并且上述block的对象类型为:
_NSConcreteStackBlock.

复杂一点的block

以上的block只是最简单的block,这里看一个稍微复杂一点的block:

int main(){
    int val1 = 123;
    int val2 = 456;
    void (^block)(void) = ^{
        printf("val1:%d",val1);
    };
    val1 = 789;
    block();
    return 0;
}

这里打印的结果大家想必都猜得出来:val1=123,而不是789.这是为什么呢?
转换后的C++代码:

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;
  int val1; // 将使用的val1作为成员变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val1, int flags=0) : val1(_val1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val1 = __cself->val1; // bound by copy

        printf("val1:%d",val1);
    }

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 val1 = 123;
    int val2 = 456;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val1));
    val1 = 789;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

对比之前的block的代码,这里稍微复杂了一点,因为涉及到了对于自动变量val1的截取.
我们可以看到在__main_block_impl_0结构体中,val1被当做了一个成员变量保存在了结构体中,同时实现函数__main_block_func_0中,打印的方式为

int val1 = __cself->val1; // __cself就代表__main_block_impl_0这个结构体变量
printf("val1:%d",val1);

我们再仔细观察一下这里:

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, 
&__main_block_desc_0_DATA, val1));

这里的val1采用的值传递,而非地址传递.所以可以解释为何后面val1 = 789,并没有更改初始值.而且我们也发现val2这个变量未曾使用到,所以在C++源码中也不会被用到.

__block 修饰符

如果我们想更改val1,这里可以用__block修饰符来声明变量:

__block int val1 = 123;

但是一旦这么声明之后,转换之后的C++代码变得稍微复杂了一些:

struct __Block_byref_val1_0 {
  void *__isa;
__Block_byref_val1_0 *__forwarding;
 int __flags;
 int __size;
 int val1;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val1_0 *val1; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val1_0 *_val1, int flags=0) : val1(_val1->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val1_0 *val1 = __cself->val1; // bound by ref

        printf("val1:%d",(val1->__forwarding->val1));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val1, (void*)src->val1, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val1, 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(){

    __attribute__((__blocks__(byref))) __Block_byref_val1_0 val1 = {
        (void*)0,
        (__Block_byref_val1_0 *)&val1, 
        0, 
        sizeof(__Block_byref_val1_0), 
        123
};
    int val2 = 456;

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val1_0 *)&val1, 570425344));

    (val1.__forwarding->val1) = 789;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

很明显我们可以看出源码中多了一个__Block_byref_val1_0这个结构体,它的作用就是用来保存我们__block修饰过的变量

struct __Block_byref_val1_0 {
  void *__isa;
__Block_byref_val1_0 *__forwarding;// 指向自己的指针
 int __flags;
 int __size;
 int val1; // 保存的成员变量
};

并且在main函数中,将123这个值初始化进了__Block_byref_val1_0结构体变量中.

__attribute__((__blocks__(byref))) __Block_byref_val1_0 val1 = {
        (void*)0,
        (__Block_byref_val1_0 *)&val1, 
        0, 
        sizeof(__Block_byref_val1_0), 
        123
};

当我们想更改val1时,这时候因为val1被存进了__Block_byref_val1_0类型的结构体中,并且传进__main_block_impl_0构造方法的是(__Block_byref_val1_0 *)&val1这个val1变量地址,所以是地址传递.而这里有一个__forwarding是指向__Block_byref_val1_0自己的,所以当设置val1 = 789的时候,可以通过(val1.__forwarding->val1) = 789来改变val1.

为什么会有__forwarding指针

这里涉及到block的作用域.在ARC情况下,编译器通常会进行适当的判断将栈上的block复制到堆里面,这个想必大家都比较清楚.
为什么需要复制到堆里呢?
因为有可能这个block在当前作用域结束的时候,block内的变量还可能需要使用.如果不复制到堆里面,那么后面就无法使用了.所以这里设置了__forwarding指针指向堆的结构体实例,不管block是在栈上,还是在堆里,都可以通过__forwarding访问到相应的成员变量.
暂时到这里吧,后面的循环引用想必都十分熟悉了,就不再一一赘述了.

相关文章

  • block系列文章总结

    iOS源码解析:Block的本质<一>iOS源码解析:Block的本质<二>Objective C block背后...

  • iOS源码解析:Block的本质<一>

    iOS源码解析:Block的本质<一> iOS源码解析:Block的本质<一>

  • [iOS]Block本质的一些研究

    一些安排和感想 从上一篇的ARC的实现细节开始,会陆续去研究一些老生常谈的话题,诸如:Block,Runtime,...

  • Objective-C的本质(6)——Block本质

    参考:iOS-Block本质iOS底层原理总结 - 探寻block的本质(一)iOS底层原理总结 - 探寻bloc...

  • iOS底层原理总结 - 探寻block的本质(一)

    iOS底层原理总结 - 探寻block的本质(一) iOS底层原理总结 - 探寻block的本质(一)

  • Block

    xx_cc iOS底层原理总结 - 探寻block的本质(一)iOS底层原理总结 - 探寻block的本质(二) ...

  • iOS原理(五)----block

    iOS原理(五)----block block的本质 block本质上也是一个OC对象,它内部也有个isa指针, ...

  • iOS-Block本质

    iOS-Block本质 参考篇:iOS-Block浅谈[https://www.jianshu.com/p/25a...

  • iOS-Block本质

    参考篇:iOS-Block浅谈 前言:本文简述Block本质,如有错误请留言指正。 第一部分:Block本质 Q:...

  • 关于block(4)

    关于block(4) 标签: iOS 技术 接上篇,我们继续探究block。 block的copy属性 研究到这里...

网友评论

      本文标题:[iOS]Block本质的一些研究

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