iOS - 揭露Block的内部实现原理

想必大家对block都很熟悉了,�虽然都会用,但是你真的知道它的原理吗?比如为什么要加上__block,这个修饰符到底有什么用?不加会有什么后果?block又是如何实现的等等。。。该篇文章就为大家揭晓关于Block的实现原理~

抛砖引玉

先给出问题,大家思考下结果吧,如果分别调用以下两个方法,结果如何?

void blockFunc1()
{
    int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}
void blockFunc2()
{
    __block int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

答案是

blockFunc1 : num equal 100
blockFunc2 : num equal 200

是不是有人答错了?再来两个函数。这两个的结果与blockFunc2一样,打印出来的 num 为 200

// 全局变量
int num = 100;
void blockFunc3()
{
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}
void blockFunc4()
{
    static int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

疑问: 我们发现num做为局部变量时加上 _ _block 修饰符、num做为全局变量以及num为静态局部变量时在block中输出结果是一样的,皆为被修改之后的值,而做为局部变量并且未加上__block的num在block中输出的值却还是未赋值之前的值。这是为什么呢?探索这个问题我们就需要看看底层结构是如何实现的了

探索内部原理

Objective-C是一个全动态语言,它的一切都是基于runtime实现的!在运行时会将OC转换成C,我们可以利用这个来查看关于block在内部是如何实现的 新建一个Command Line Tool项目,将以上代码放入main.m中,如图

main.m

这里我们打开终端,cd到项目目录下,然后将用下面的命令将OC重写为C

clang -rewrite-objc main.m

rewrite-objc

这时我们可以发现当前目录下多了一个main.cpp文件,打开它并滚到最下面

打开main.cpp

main.cpp

这里我们可以看到blockFunc1的C语言实现方法

void blockFunc1()
{
    int num = 100;
    void (*block)() = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

去掉类型转换

void blockFunc1()
{
    int num = 100;
    // *************************重点句***********************
    void (*block)() = &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    // *****************************************************
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

这里我们可以看到

block实际上是指向结构体的指针

该结构体为

__blockFunc1_block_impl_0

我们来看下带__block的blockFunc2

blockFunc2

在 blockFunc1 中,block指向了一个名为__blockFunc1_block_impl_0的结构体,并且在初始化时输入了三个参数(__blockFunc1_block_impl_0最后的flags有默认参数,所以可以不用传参),第三个参数就是我们写的num,与blockFunc2相比较,这里的num并没有带*号,所以说在这里它只是传值而非传址,而下面的【num = 200;】也就没什么卵用了。这就是blockFunc2、blockFunc3与blockFunc4为什么能打印出num改变后的值,而blockFunc1不行的原因。

在这里我们也可以看出:

编译器会将block的内部代码生成对应的函数

** SO **

我们总结下,block在内部会作为一个指向结构体的指针,当调用block的时候其实就是根据block对�应的指针找到相应的函数,进而进行调用,并传入自身

__block的实现

我们再来看看 _ block, _block也被转换成了结构体,并含有5个变量

struct __Block_byref_num_0 {
  void *__isa;  // isa指针
__Block_byref_num_0 *__forwarding;  // 实例本身
 int __flags; 
 int __size;
 int num;  // 我们的num值
};

图片对应着blockFunc2中的

__block int num = 100;

当创建num并用__block修饰的时候,会初始化这五个变量 当我们执行

num = 200;

对应着

(num.__forwarding->num) = 200;

上面刚刚提到过 _ _forwarding是实例本身,�即类型结构体__Block_byref_num_0的&num,再找到对应的num变量,将其原来的100修改为200~~

到此,关于Block内部实现的揭晓也就到此结束了,希望本文能让你对block有更深的理解,感谢你耐心的阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微信公众号:Java团长

Java的三种代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即...

611
来自专栏自动化测试实战

RF自定义系统关键字

4007
来自专栏Kirito的技术分享

警惕不规范的变量命名

就在最近,项目组开始强调开发规范了,今天分享一个变量名命名不规范的小案例,强调一下规范的重要性。 Boolean变量名命名规范 16年底,阿里公开了《Java...

3469
来自专栏python爬虫日记

转载:python的编码处理(一)

最近业务中需要用 Python 写一些脚本。尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。 

1042
来自专栏西枫里博客

PHP7新特性之两个小小语法糖。

想起写下这篇原本是因为群里龙大佬说PHP7下count有问题,顺道就讽了他一句。其实我自己也没有详细了解下PHP7到底在哪些方面做了修改。所以空了就翻了翻手册,...

1691
来自专栏linux驱动个人学习

Linux 内存池【转】

1665
来自专栏老付的网络博客

程序如何运行的

在写代码的时候,我们直接在没有编译报错的时候,直接点击运行后,ide会直接把程序的结果输出到控制台上,代码如下:

2025
来自专栏散尽浮华

Python介绍

Python概述 Python创始人是吉多.范罗苏姆。在1989年万圣节期间为打发时间而开发的。 目前Python在TIOBE排行榜第五位置 ? Python可...

43810
来自专栏java达人

数据字典项实现方案

在应用开发中,总会遇到许多数据字典项,比如对象状态、对象类型等等,这些项一般都是固定的若干可选值选项,比如对象状态可能有新建、修改、删除等状态,这些数据字典项一...

1.1K7
来自专栏架构师小秘圈

shell脚本极简教程

一,shell题记 不懂shell的程序员不是好程序员,学习shell是为了自动化,使用自动化可以非常有效的提高工作效率。没有一个大公司不要求linux的基本技...

4326

扫码关注云+社区

领取腾讯云代金券