美文网首页iOS
Block 的实现原理 (一)

Block 的实现原理 (一)

作者: 小新0514 | 来源:发表于2019-02-27 11:27 被阅读7次

本篇文章主要是使用 clang 重写 dispatch_block_t 类型对象.

最基础的 Block

- (void)test {
    dispatch_block_t block = ^ {
        
    };
    block();
}

这个语法块是一个最基础的 Block, 使用:

clang -rewrite-objc fileName.m
命令重写所在文件, 可以得出 Block 相关的 C++ 版本实现函数:

//重写后的 block
struct __TestObject__test_block_impl_0 {
  struct __block_impl impl; //block 的基础结构
  struct __TestObject__test_block_desc_0* Desc; //block 的描述
  __TestObject__test_block_impl_0(void *fp, struct __TestObject__test_block_desc_0 *desc, int flags=0) { //构造函数
    impl.isa = &_NSConcreteStackBlock; //语法块类型
    impl.Flags = flags; //标记位
    impl.FuncPtr = fp; //函数指针, FuncPtr 是 void * 型指针
    Desc = desc; //描述
  }
};

//block 执行部分被重写为一个函数
static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
     //block 执行函数内部自带隐形参数 __cself
     int i = 0;
}

//用来描述 block 的结构体
static struct __TestObject__test_block_desc_0 {
  size_t reserved; //reserved 的原意是保留
  size_t Block_size; //block 占用内存大小
} __TestObject__test_block_desc_0_DATA = { 0, sizeof(struct __TestObject__test_block_impl_0)}; //构造函数

//重写后的 test 函数:
static void _I_TestObject_test(TestObject * self, SEL _cmd) {
    dispatch_block_t block = ((void (*)())&__TestObject__test_block_impl_0((void *)__TestObject__test_block_func_0, &__TestObject__test_block_desc_0_DATA)); //创建一个 block 并进行初始化
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); //调用 block, 将 block 本身作为参数传入
}

一个最简单的 block 被重写后会分为 block 结构体本身, block 代码段函数, block 描述结构体三个部分, 这就是 block 的实质.

有返回值的 block

- (void)test {
    int (^block)(void) = ^ {
        return 1;
    };
    block();
}

重写后

//block 执行函数有 int 型返回值
static int __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
     return 1;
}

static void _I_TestObject_test(TestObject * self, SEL _cmd) {
    int (*block)(void) = (...((void *)__TestObject__test_block_func_0, ...)); //将 block 执行函数的函数指针转换成 void * 型
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); //调用前需要转换回原类型
}

基本上不同的地方只有 block 的执行函数变成了有返回值的函数, 但依旧会转换成 void * 型指针来存储, 在调用之前再将指针类型转换回来, 然后再进行调用.

使用外部变量的 block

我们知道 block 里面可以使用局部变量、成员变量以及全局变量等外部变量, 那使用外部变量的 block 重写后会变成什么样子.

- (void)test {
    NSObject * obj = [[NSObject alloc] init];
    dispatch_block_t block = ^ {
        obj;
    };
    block();
}

重写后去掉刚刚提到过的部分:

struct __TestObject__test_block_impl_0 {
    ...
    NSObject *obj; //原 block 结构体会增加一个同名同类型变量
    __TestObject__test_block_impl_0(..., NSObject *_obj, ...) : obj(_obj) { //构造函数会自动将参数 _i 赋值给成员 i
        ...
    }
}

static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
     NSObject *obj = __cself->obj; //函数内部会通过隐藏参数 __cself 找到 block 结构体中的成员 obj
     obj;
}

//出现了两个函数, 一个名为 copy, 一个名为 dispose
static void __TestObject__test_block_copy_0(struct __TestObject__test_block_impl_0*dst, struct __TestObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __TestObject__test_block_dispose_0(struct __TestObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->i, 3/*BLOCK_FIELD_IS_OBJECT*/);}

//新的函数被包含进 block 的描述结构体里面
static struct __TestObject__test_block_desc_0 {
    ...
    void (*copy)(struct __TestObject__test_block_impl_0*, struct __TestObject__test_block_impl_0*);
    void (*dispose)(struct __TestObject__test_block_impl_0*);
} __TestObject__test_block_desc_0_DATA = { ..., __TestObject__test_block_copy_0, __TestObject__test_block_dispose_0}; //使用构造函数构造两个新的函数

static void _I_TestObject_test(TestObject * self, SEL _cmd) {
    NSObject * obj = ...;
    dispatch_block_t block = (..., obj, ...); //创建时将 obj 传入构造函数
    ....
}

可以看到, 使用到局部变量 obj 的 block 只是在内部多了一个一模一样的变量. 同时多了 copy 和 dispose 方法,
这两个函数是给 block 内的 obj 赋值用的, 本例中的 obj 的引用计数会 + 1, 所以如果是 self.block 内部使用 self 的话, 就会因为 copy 函数的原因, 导致循环引用. 使用局部变量和成员变量是这种情况.
我们看到, 这里的 obj 是直接传递给构造函数的, 如果使用的不是 OC 对象, 而是基础数据类型 int i 的话, 这里传入的就是 C 语言中常说的形参, 所以 block 内部对基础数据类型的操作都无法还原回原本的变量上面.

为局部变量添加 __block 标记

已经将 block 运用自如的朋友们应该对 __block 标记并不陌生, 这个标记用于解决刚才提到的, 语法块内部无法修改外部局部变量的问题. 那 __block 究竟是如何工作的, 我们继续重写一下看看.

- (void)test {
    __block int i = 0; //使用 __block 来标记局部变量 i
    dispatch_block_t block = ^ {
        i = 1; //这里允许更改
    };
    block();
}

重写后:

//出现了一个新的结构体
struct __Block_byref_i_0 {
     void *__isa;
    __Block_byref_i_0 *__forwarding; //本类型指针
     int __flags;
     int __size;
     int i;
};

struct __TestObject__test_block_impl_0 {
    ...
    __Block_byref_i_0 *i; //i 变成了一个 __Block_byref_i_0 * 类型的结构体指针
    __TestObject__test_block_impl_0(..., __Block_byref_i_0 *_i, ...) : i(_i->__forwarding) { //构造函数将参数 _i->__forwarding 赋值给 i
        ...
    }
}

static void __TestObject__test_block_func_0(struct __TestObject__test_block_impl_0 *__cself) {
    __Block_byref_i_0 *i = __cself->i; //函数内部也开始使用新的结构体指针 i
    (i->__forwarding->i) = 1; //给 i 赋值
}

static void _I_TestObject_test(TestObject * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0}; //使用 __Block_byref_i_0 结构体将局部变量 i 封装起来, __forwarding 赋值为自身的地址
    dispatch_block_t block = (..., (__Block_byref_i_0 *)&i, 570425344); //创建时将 i 的地址传入构造函数, 并且标记位传入了新的值
    ....
}

可以看到 __block 标记相比于之前直接使用外部变量的方式, 独特了很多. 支持 __block 标记的内容主要是结构体 __Block_byref_i_0. 依靠的方式就是将被 __block 标记的局部变量, 用一个新的结构体封装好, 之后把构造好的结构体的地址传入 block 的构造函数, 并在应用中使用对应的结构体指针.

到这里简单的将 block 的几种形式重写了一下, 看了一下大致的实现思想, 下一篇文章将深入 block 的源码, 详细分析 block 的实现原理.

相关文章

  • iOS-2 Block

    block块 系列文章: iOS Block浅浅析 - 简书 iOS Block实现原理 iOS Block __...

  • iOS Block 部分一

    主要讲解 Block 的底层实现原理; Block部分一Block部分二Block部分三Block知识点总结 基础...

  • iOS Block存储域及循环引用

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block __block说明符...

  • 深入研究Block用weakSelf、strongSelf、@w

    前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理。然而实际使用Block过程...

  • iOS Block概念、语法及基本使用

    系列文章:iOS Block实现原理iOS Block __block说明符 最近又翻了一遍《Objective-...

  • Block

    Block 1.Block的定义和语法2.Block的本质和分类3.__block的实现原理 Block的定义和语...

  • Today面试

    Runloop 底层原理Kvo 底层原理ARC 底层原理 如何实现GCD 底层原理Block 底层原理Aut...

  • iOS Block浅析

    Block实现原理 要想知道Block的内部实现,需要知道Block编译完后是什么样子,使用clang可看到Blo...

  • iOS Block __block说明符

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block存储域及循环引用 上一...

  • Block 的实现原理 (一)

    本篇文章主要是使用 clang 重写 dispatch_block_t 类型对象. 最基础的 Block 这个语法...

网友评论

    本文标题:Block 的实现原理 (一)

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