新建一个命令行项目,代码如下:

#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {}return 0;
}void test(){int a = 2;int b = 3;long (^myBlock)(void) = ^long() {return a * b;};long r = myBlock();
}

使用编译指令编译成 cpp 文件查看源码:

xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc main.m -o main.cpp

得到的原始代码中 test 函数的源码为:

void test(){int a = 2;int b = 3;long (*myBlock)(void) = ((long (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, b));long r = ((long (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

去掉强制类型转换,简化代码:

void test(){int a = 2;int b = 3;long (*myBlock)(void) = &__test_block_impl_0(__test_block_func_0, &__test_block_desc_0_DATA, a, b));long r = (myBlock->FuncPtr)(myBlock);
}

其实还是有点懵逼的,再认真分析下第一行代码:

long (*myBlock)(void) = &__test_block_impl_0(__test_block_func_0, &__test_block_desc_0_DATA, a, b));

从代码中可以看到这行代码定义了一个函数类型的指针myBlock,并且将其指向了__test_block_impl_0返回值的地址。这里把内存地址赋值给指针是指针用法的常规操作,就不多说了。于是乎就会想看看__test_block_impl_0这是个什么东东,源码如下:

struct __test_block_impl_0 {struct __block_impl impl;struct __test_block_desc_0* Desc;int a;int b;__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

这里需要解释的是:

  1. __test_block_impl_0()函数和结构体同名,这里是 C++ 语法,C语言的结构体不允许存在函数;
  2. : a(_a), b(_b)冒号这种语法是 C++ 中的构造函数中初始化成员列表,也就是将入参直接赋值给成员 a 和 b,简化了代码。类似的语法在很多其他语言中也存在,比如 Dart;

所以就可以得出结果,第一行代码的作用就是:创建一个__test_block_impl_0变量,并且赋值给myBlock

这里预留个疑问:
这个long (*myBlock)(void)是声明一个指向函数的指针。可是明明是指向结构体变量的指针,可以使用struct __test_block_impl_0 *p来写,代码如下:

struct __test_block_impl_0 block;
struct __test_block_impl_0 *myBlock = &block;

可是问什么要声明成一个函数指针呢?先留着吧~

继续,第一行看完了,知道是在做啥了,那现在就应该看看__test_block_impl_0到底是个啥玩意了,还是先看源码:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};struct __test_block_impl_0 {// 特别注意,这里impl不是指针哦,所以相当于直接将__block_impl中的四个成员复制过来struct __block_impl impl;struct __test_block_desc_0* Desc;int a;int b;__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {// 默认赋值为_NSConcreteStackBlock,但是还有其他的类对象类型impl.isa = &_NSConcreteStackBlock;// 默认是0,不用理他impl.Flags = flags;// 这里就是封装了block内部逻辑的函数impl.FuncPtr = fp;// 计算size用的,不用理Desc = desc;}
};long (*myBlock)(void) = ((long (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, b));

其实第一行代码可以简化成:

// 原来的代码
long (*myBlock)(void) = &__test_block_impl_0(__test_block_func_0, &__test_block_desc_0_DATA, a, b));// 简化:
struct __test_block_impl_0 block;
struct __test_block_impl_0 *myBlock = &block;
myBlock->impl.isa = &_NSConcreteStackBlock;
myBlock->impl.Flags = 0;
myBlock->impl.FuncPtr = __test_block_func_0;
myBlock->Desc = &__test_block_desc_0_DATA;
myBlock->a = a;
myBlock->b = b;

这样就很清晰了,继续看__test_block_func_0__test_block_desc_0_DATA,看源码:

// __test_block_func_0源码:
static long __test_block_func_0(struct __test_block_impl_0 *__cself) {int a = __cself->a;int b = __cself->b;return a * b;
}// __test_block_desc_0_DATA的源码:
static struct __test_block_desc_0 {size_t reserved;size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};

从源码可知:

  1. __test_block_func_0将 block 内部的逻辑封装成了一个函数;
  2. __test_block_desc_0生成了一个__test_block_desc_0_DATA的变量,reserved为占位保留字段,不用管,而__test_block_impl_0结构体(也就是 block 结构体的第一个成员),的内存大小被赋值给了__test_block_impl_0

这样就可以看到本质了:

  • block 本质是一个 C++ 结构体
    block 是通过 C++ 的结构体而不是 C 的结构体实现的。被捕获的变量会生成为 block 的成员变量。内部的成员变量impl包含了面向对象的基础和 block 内部代码逻辑的实现。而构造函数则执行了赋值操作,完成了结构体变量的初始化。
  • block 也是一个对象
    block 的第一个成员是__block_impl结构体,而__block_impl第一个成员就是isa指针,所以,block 的本质就是对象,默认指向_NSConcreteStackBlock类,也就是 block 本质是一个_NSConcreteStackBlock类的对象(默认情况下,后面还会解释)

继续 go on,再看看第二行代码:

long r = ((long (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

去掉强制类型转换后:

long r = myBlock->FuncPtr(myBlock);

也就是直接调用FuncPtr这个函数,参数为myBlock

这里有两个疑问:

  1. 函数指针类型为什么不是直接调用而需要通过 ->来访问内部成员变量调用呢?
    正常来讲,函数指针的使用如下:
long func(int a) {return a;
}long (*p)(int) = &func;
printf("%li\n", p(10)); // 结果为10

但是呢,通过上面的源码分析可以看到 myBlock这个变量并不是一个指向函数的指针,而是一个 struct __test_block_impl_0 类型的指针,所以如果不看他的类型,正常使用->访问成员变量是逻辑正确的,所以这个问题本质仍然是为什么要用long (*myBlock)(void)类型的指针来指向struct __test_block_impl_0类型的指针,同上,暂时先不管~~~

  1. FuncPtr是内部成员变量impl的成员变量,为什么可以直接访问?
    正常来讲应该这样访问:
long r = myBlock->impl.FuncPtr(myBlock);

因为impl是结构体的第一个成员变量,所以其内存地址就是结构体变量的地址,也就是说这个地址就是impl成员变量的地址,三者是等价的,也就是说:myBlock = &myBlock.impl = &block(参照上文的最终简化的代码逻辑),所以这样直接调用impl.FuncPtr就是正确的。

这个结论还可以通过内存地址来验证,test()函数中内容修改如下:

void test(){int a = 2;int b = 3;long (^myBlock)(void) = ^long() {return a * b;};struct __test_block_impl_0 *p = (__bridge struct __test_block_impl_0 *)myBlock;long r = myBlock();
}

运行后打断点:

继续断点至return a * b;,并设置 Debug Workflow为 Always Sow Disassembly,的到如下结果:

也就是说三个地址相互吻合。

备注

  1. myBlock 中的isa指向变成了NSMallocBlock,这个以后会继续讲解
  2. struct __test_block_impl_0 *p = (__bridge struct __test_block_impl_0 *)myBlock;这里在myBlock前面没有加 & ,因为myBlock本身就是一个指针,其值就是它指向的内存地址,如果加了 & 意思是取myBlock的内存地址,这样就不对了~

至此,Block的基本原理完毕,下一篇Block进阶~

iOS源码分析:Block的本质相关推荐

  1. Retrofit2.0 源码分析

    前言 注解式的框架非常火,注解以其轻量,简洁等特性被人们所喜爱者,关键是它解藕.网络请求的框架非常多,比较受欢迎的当属retrofit和okHttp了.连retrofit都是基于okHttp之上开发的 ...

  2. Java高并发之CountDownLatch源码分析

    概述 CountDownLatch 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助.简单来说,就是 CountDownLatch 内部维护了一个计数器,每个线程完成自己的操作之后都 ...

  3. phonegap源码分析(三)------ IOS

    几个月前看过phonegap在Android和WP上的实现源码,当时苦于没mac环境,直到现在才抽出时间学习了一下phonegap是如何让JS与native串联起来的. phonegap在IOS上和在 ...

  4. HTTP服务器的本质:tinyhttpd源码分析及拓展

    已经有一个月没有更新博客了,一方面是因为平时太忙了,另一方面是想积攒一些干货进行分享.最近主要是做了一些开源项目的源码分析工作,有c项目也有python项目,想提升一下内功,今天分享一下tinyhtt ...

  5. 转 MySQL源码分析

    看到一个不错的介绍,原址如下: http://software.intel.com/zh-cn/blogs/2010/08/20/mysql0/ MySQL源码分析(0):编译安装及调试 作者: Yu ...

  6. Spark源码分析之七:Task运行(一)

    在Task调度相关的两篇文章<Spark源码分析之五:Task调度(一)>与<Spark源码分析之六:Task调度(二)>中,我们大致了解了Task调度相关的主要逻辑,并且在T ...

  7. java bufferedwrite_Java BufferedWriter BufferedReader 源码分析

    一:BufferedWriter 1.类功能简介: BufferedWriter.缓存字符输出流.他的功能是为传入的底层字符输出流提供缓存功能.同样当使用底层字符输出流向目的地中写入字符或者字符数组时 ...

  8. YYCache 源码分析(一)

    iOS 开发中总会用到各种缓存,YYCache或许是你最好的选择.性能上有优势,用法也很简单.作者ibireme曾经对比过同类轮子:http://blog.ibireme.com/2015/10/26 ...

  9. openmp官方源码_MNN推理过程源码分析笔记(一)主流程

    在正式开始推理代码分析之前, 回顾下 MNN整体结构 推理分为三个大部分 Engine Backends Runtime Optimize 那么问题来了,从哪里开始,怎么入手呢? 我的心得是源码分析不 ...

  10. DialogFragment源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 目录介绍 1.最简单的使用方法 1.1 官方建议 1.2 最简单的使用方法 1.3 DialogFragment做屏幕适配 2 ...

最新文章

  1. 《Effective Java》读书笔记--创建和销毁对象
  2. 爬虫入门到精通-HTTP协议的讲解
  3. 超级计算机的缺点,超级计算机也无法算完圆周率,反而会死机?说出来你一定不会相信...
  4. Java冒泡实现类Collections.sort()
  5. python 映射和反映射_python映射类型的相关介绍
  6. 禁止微信公众号页面上下滑动
  7. python三维模型_python三维模型
  8. 如何解决IE6双边距问题?
  9. 前端需要了解的nginx(2)
  10. android 混合现实,基于Android的增强现实客户端的设计与实现
  11. 云栖日报丨收购中天微,阿里芯了解一下!
  12. 超声乳化设备行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  13. Microsoft Visio 2010简体中文版
  14. JMeter录制脚本和参数化
  15. 桌面云之深信服VMP管理
  16. 机器学习算法工程师面试考点汇总
  17. 对九个超级程序员的采访
  18. python根据身高计算标准体重_有谁知道如何根据身高计算标准体重
  19. mac 下 jkl 按键失灵
  20. Vue 图片加载错误处理(显示默认图片)

热门文章

  1. 冒泡排序(C#)实现
  2. 怎么用几何画板作一些简单的图形
  3. c#开发之八---mvc
  4. 很喜欢博客园这个平台
  5. 纯软件归档产品的好处
  6. winform datagridview 打印预览
  7. SQL SERVER: 合并相关操作(Union,Except,Intersect)
  8. [转]22条经典的编程引言
  9. c语言编写keil 设置memory model的编辑器,keil C51的Memory Model 说明[三种Model的选择对编译的影响]】...
  10. 【android自定义控件】ProgressBar自定义