0%

UIStackView是在iOS9推出的,最近项目中刚好用到,对UIStackView的使用和一些属性做一个记录。

简介

A streamlined interface for laying out a collection of views in either a column or a row.

这是苹果官方对这个UI控件的定义,翻译过来就是一个用于在中布置视图集合的流线型界面。我们可以用UIStackView创建自动适配屏幕方向、屏幕大小等变化布局视图。

关键属性

主要介绍几个使用时会大概率用到的属性。

axix属性

这个很好理解,就是确定视图的布局方向,有两个取值:

1
2
3
4
public enum Axis : Int {
case horizontal = 0
case vertical = 1
}

看字面就知道其意思,horizontal表示水平布局,vertical表示垂直布局。

spacing

每个arranged view之间的空隙。

distribution属性

决定了axix设置值方向的布局,有五种取值:

1
2
3
4
5
6
7
public enum Distribution : Int {
case fill = 0
case fillEqually = 1
case fillProportionally = 2
case equalSpacing = 3
case equalCentering = 4
}
  • fill:arranged views会改变size以用来填满stack view的axis方向的空间,如果arranged views在stack view空间内不能显示完整,arranged views会等比例缩小,如果arranged views不能填充满stack view,views将会拉伸以填充满。

    distribute_fillroportionally_2x_4a83cd74-be8d-4ef1-adf9-c5252a1bcc65

  • fillEqually: arranged views会自动适配大小,以填充满axis方向的空间,所有arranged views在axis设置的方向上长度相等。

    distribute_fillequally_2x_5ccda608-869a-48b9-9515-9b6314d091a9

  • fillProportionally:arranged views会自动适配大小,以填充满axis方向的空间,arranged views在axis设置的方向上长度会根据它们实际的大小比例缩放或拉伸。

    distribute_fillroportionally_2x_4a83cd74-be8d-4ef1-adf9-c5252a1bcc65 (1)

  • equalSpacing:arranged views会自动适配大小,以填充满axis方向的空间,这里和fill不同的是,如果arranged views不能填充满stack view,每个view之间会添加相同长度的space,同样如果arranged views在stack view`空间内不能显示完整,arranged views会等比例缩小。

    distribute_equalspacing_2x_6668568b-a445-402c-94ae-f5e85b0b10bd

  • equalCentering:每个arranged views在axis方向上中心点的距离相等。

    distribute_equalcentering_2x_7089d0d3-f161-452b-ab3e-9885c7b6101e

alignment 属性

这个决定了view在axis设置方向的垂直方向的布局,有六个枚举值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public enum Alignment : Int {
/* Align the leading and trailing edges of vertically stacked items
or the top and bottom edges of horizontally stacked items tightly to the container.
*/
case fill = 0
/* Align the leading edges of vertically stacked items
or the top edges of horizontally stacked items tightly to the relevant edge
of the container
*/
case leading = 1
public static var top: UIStackView.Alignment { get }
case firstBaseline = 2 // Valid for horizontal axis only

/* Center the items in a vertical stack horizontally
or the items in a horizontal stack vertically
*/
case center = 3
/* Align the trailing edges of vertically stacked items
or the bottom edges of horizontally stacked items tightly to the relevant
edge of the container
*/
case trailing = 4
public static var bottom: UIStackView.Alignment { get }
case lastBaseline = 5 // Valid for horizontal axis only
}
  • fill:arranged views会改变size以用来填满stack view的axis垂直方向的空间。

    align_fill_2x_8d71867d-e6cf-4063-b337-17dbc815c16e

  • leading/top:

    • leading:axis设置为vertical时生效,垂直axis方向上居前对齐。

      align_leading_2x_bd31ee78-682d-4e36-990e-d655505fdc95

    • top:axis设置为horizontal时生效,垂直axis方向上居上对齐。

      align_top_2x_bfa21a2d-1678-4b11-aa80-0750a4534bfc

  • firstBaseline:axis垂直方向首行以baseline对齐,仅axis设置为vertical时生效。

    align_firstbaseline_2x_8b939a0f-5296-45d2-836c-aa05b4432e12

  • center:中间对齐。

    align_center_2x_a34c8513-6f32-4cac-8149-4e4c1d206a3a

  • trailing/bottom:

    • trailing:axis设置为vertical时生效,垂直axis方向上居后对齐。

      align_leading_2_2x_61cdf9c4-2a5b-4a3e-9c13-b0f1fa6bf348

    • bottom:axis设置为horizontal时生效,垂直axis方向上居底对齐。

      align_bottom_2x_2dc738dd-2d3a-4f7b-baee-aa283fe41e9f

  • lastBaseline:末尾baseline对齐仅axis设置为horizontal时生效。

    align_lastbaseline_2x_82af7014-4e27-450d-9115-b058217de073

用法

UIStackView特别适合那种视图个数不定的场景,例如一行显示的元素个数受数据源状态字段的影响。

  • 实例化UIStackView的一个实例:

    1
    2
    3
    4
    let stackView = UIStackView(frame: CGRect.init(x: 15, y: 40, width: 350, height: 30))
    stackView.axis = .horizontal///水平方向
    stackView.spacing = 10.0///每个view间距
    stackView.distribution = .fillEqually///等宽
  • UIStackView中添加子arranged view

    1
    2
    3
    stackView.addArrangedSubview(label1)
    stackView.addArrangedSubview(label2)
    stackView.addArrangedSubview(label3)
  • 改变外界数据条件,以隐藏一个arranged view:

    1
    2
    isHideLabel2 = !isHideLabel2///外部数据条件
    label2.isHidden = isHideLabel2///修改隐藏

    注意这里有一个细节,就是当arranged viewisHidden属性设置为false的时候,stackView会自动调整布局。

  • 如果我们需要动画效果,也可以通过UIView的方法添加:

    1
    2
    3
    4
    isHideLabel2 = !isHideLabel2
    UIView.animate(withDuration: 0.25) {
    self.label2.isHidden = self.isHideLabel2
    }

显示的效果:

1634210839500

隐藏的效果:

1634210857755

总结

这里主要介绍了UIStackView的一些常用属性,和一个简单的用法,如果有更好的用法或者写的不准确的地方欢迎批评指正。

上一篇文章我们简单介绍了可执行文件Mach-O的结构,本篇我们主要看一些内存的理论知识,从物理内存管理时代开始。

直接物理内存管理

这种加载方式比较简单暴力,当我们打开一个应用的时候会将整个应用都加载到物理内存上去,应用程序访问的是实际的物理内存地址,示意图如下:

物理内存管理示意图

这种管理方式挺好理解的,但是会有两个问题:

  • 内存不够用:如上图所示,A、B、C三个应用已经加载到内存了,如果现在我们要加载E应用到内存,此时物理内存的剩下的空间不够就会导致应用E打开失败,必须关闭A、B、C其中一个应用才能正常打开应用E。
  • 应用的安全问题:因为现在应用程序是直接访问物理内存,假如B是一个恶意软件,它就可以通过自己应用的内存地址通过偏移找到应用C访问的内存地址,这样就不能保证应用C的安全。

为了解决内存不够用的问题,提出了分页管理内存的概念,就是把每个应用使用的内存分页管理,每次只是将使用到的加载到内存里,这样就大大增加了内存的利用率,示意图如下:

分页内存管理示意图

可以看到采用分页管理的确可以解决内存不够用的问题,同一时间可以有更多的活跃应用,但是这种方式会引起一个新的问题,就是单个应用访问的内存地址不连续,不连续提高了管理的复杂性,应用安全的问题依然存在,所以后面出现了我们现在常用的虚拟内存管理方式。

虚拟内存管理

每个应用运行以后,它将拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小由计算机硬件平台决定,具体说就是由CPU的位数决定的。硬件决定了地址空间的理论上限,注意这里说的是理论上,理论64位的iPhone的虚拟内存地址可以是2^64字节的大小,但是iPhone6s之后的机型的虚拟内存空间大小是4GB(1GB被操作系统使用、3GB应用使用)。

虚拟内存管理大致的示意图如下:

虚拟内存管理示意图

可以看到每个应用都有自己独立且连续的虚拟内存空间,虚拟地址通过MMU这个硬件进行映射找到实际的物理地址,达到访问使用物理地址的功能。从程序点击启动,到运行操作系统角度来看一共经历了三件事情:

  • 创建一个独立的虚拟地址空间,实际上只是分配一个页目录就可以,映射关系等后面程序发生页错误的时候再进行设置。
  • 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系,后面我们会提到缺页错误,当发生缺页错误的时候,操作系统将从物理内存中分配一个物理页,然后将该缺页从磁盘中读取到内存中,在设置缺页的虚拟页和物理页的映射关系,这样才能正常运行,这时候就是使用了虚拟空间地址和可执行文件之间的映射关系。
  • 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行。

可以看出虚拟内存同时解决了内存不够用和应用安全的问题,因为每个应用程序是独立的虚拟内存,不能访问其他程序的内存也就达不到攻击的目的。

缺页错误

我们知道采用分页管理的时候,应用程序不是一下在都加载到内存的,当我们访问的数据或指令不在内存里的时候,就需要加载访问数据或指令所在的页,这个情况就要缺页错误(page fault)。

在抖音团队的iOS启动优化《原理篇》,有提到这一点,一次pagefault所消耗的时间是毫秒级别的,但是在我们应用启动的时候会有大量的pagefault,这样我们可以使用二进制重排的方式减少pagefault的次数,从而达到启动优化的目的,感兴趣的同学可以研究一下。

应用虚拟内存空间分布

应用在虚拟内存的分布如下图所示:

虚拟内存区域分布.png

可以看到应用程序能访问到的是五个区域:

  • 代码区(.text):通过镜像文件加载到内存的代码,权限只读,可执行。
  • 已初始化数据(.data):通过镜像文件加载到内存的已经初始化的数据(全局变量、静态变量),权限可读写,可执行。
  • 未初始化数据(.bass):通过镜像文件加载到内存的未初始化的数据(全局变量、静态变量),权限可读写,可执行。
  • 堆区(heap):通过alloc等方法新建的对象所在的区域,我们平时说的ARC的内存管理就是对这个区域的管理,权限可读写,可执行,但是不能通过镜像文件加载恢复,地址向上扩展。
  • 栈区(stack):主要存储是函数、方法以及一些参数,权限可读写,不可执行,不能通过镜像文件恢复,地址向下扩展。

补充:保留区并不是一个单一的内存区域,而是对内存中受到保护二禁止访问的内存区域的总称,例如大部分操作系统,极小的地址通常都是不语序访问的,如NULL。

总结

本篇我们主要介绍了内存的一些理论知识,对目前主流的内存管理方案虚拟内存和应用加载到内存分布的五大区有了一定了解,它对我们理解应用的加载和内存管理的内容有很大帮助,iOS应用的加载流程和内存管理方案在后面的文章中继续探索。

上一篇我们介绍了编译的流程,本篇我们主要看编译产物Mach-O文件的内容。

目标文件

编译器编译源代码后生成的文件就叫目标文件,Windows平台下PE(Portable Executable)和Linux下是ELF(Executalbe Linkable Format),它们都是COFF格式的变种,目标文件就是源代码编译后但未进行链接的中间文件(Windows的.obj和Linux下的.o),它和可执行文件的结构很相似,从广义上看,目标文件和可执行文件的格式几乎是一样的,所以我们可以广义的将目标文件和可执行文件看成是一种类型的文件,本文我们主要看iOS/macOS平台的目标文件Mach-O的内容。

Mach-O文件内容概况

使用MachOView工具,我们查看一个Mach-O文件的内容

macho文件概览

可以看到主要分为三部分:

  • Header:它描述了整个文件的基本属性,比如文件版本、目标机器型号、程序入口地址等。
  • Load Commands:它包含了很多表,描述了文件中数据的组织结构,不同的数据类型使用不同的加载命令标识。
  • Data:目标文件最大的部分,包含Segment的具体数据。

我们先看Header部分,测试工程的字段值如下:

macho-header

我们也可以在iOS的sdk的loader.h文件中找到定义:

1
2
3
4
5
6
7
8
9
10
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};

可以看到Header主要包括:

  • Magic Number:魔数,它是文件的标识码,用于标识当前设备是大端序还是小端序,有 MH_MAGIC_64MH_CIGAM 两种形式 。
  • CPU Type:表示cpu的架构,包括ARM64X86_64i386等。
  • CPU SubType:表示具体cpu的类型,区分不同版本的处理器。
  • File Type:表示Mach-O文件类型,有MH_EXECUTE(可执行文件)、MH_OBJECT(可重定向的目标文件)、MH_DYLIB(动态绑定的共享库文件)等。
  • Number of Load Commands:表示文件中Load Commads(加载命令)的个数。
  • Size of Load Commands:表示文件中Load Commads(加载命令)所占的总字节大小。
  • Flags:不同的文件标签的组合,每个标签占一个位,可以用位或来进行组合,常见的标签有
    • MH_NOUNDEFS:该文件没有未定义的引用。
    • MH_DYLDLINK:该文件将要作为动态链接器的输入,不能再被静态链接器修改。
    • MH_TWOLEVEL: 该文件使用两级名字空间绑定
    • MH_PIE: 可执行文件会被加载到随机地址,只对MH_EXECUTE有效,意思是启用ALSR
  • Reserved:保留字段。

Mach-O文件被系统装载的时候,会先读取Header部分,通过Header找到Load Commands加载指令部分,读取到加载指令就可以加载到我们编写的代码。

Load Commands

Load Commands描述了Data中的各个段信息,比如每个段的段名、段的长度、在文件中的偏移、读取权限以及段的其他属性,它的起始位置要由Header的大小决定。

LoadCommads

由上图可以看到LoadCommad由很多段构成:

  • LCSEGMEMT64(_PAGEZERO):该段在虚拟内存中的位置及大小都为0,不可读、不可写、不可执行,用来处理空指针,如果试图访问该段,那么将会引起系统的崩溃。
  • LCSEGMEMT64(_TEXT):可执行的代码和其他一些只读数据,映射到内存,紧跟随_PAGEZERO端之后。
  • LCSEGMEMT64(_DATA):可以更改的数据,映射到内存,紧跟随_TEXT端之后。
  • LCSEGMEMT64(_LINKEDIT):动态链接库的原始数据,如符号、字符串和重定位表条目等。
  • LC_DYLD_INFO_ONLY:保存动态链接的重要信息,根据它来进行地址重定向.根据它所记录的偏移量,我们便可以找到在 Dynamic Loader Info 中的相关信息。
  • LC_SYMTAB:文件所使用的符号表,找到后分别获取符号表偏移量,符号数,字符串标偏移量,字符串表大小。
  • LC_DYSYMTAB:动态链接器所使用的符号表,找到后获取间接符号表偏移符申表偏移量。
  • LC_LOAD DYLINKER:默认的加载器路径(/usr/bin/dyld)
  • LCUD: Mach-O文件的唯一标识(UUID)
  • LC_VERSION MIN IPHONEOS: Mach-O文件要求的最低系统版本,和Xcod配置的 target有关。
  • LC_SOURCE VERSION:构建二进制文件的源代码版本。
  • LC_MAIN:程序的入口,包括入口的OffsetPoint
  • LC_ENCRYPTION_INFO64:文件加密信息,包括加密标记、加密数据的偏移和大小。Crypt Id为1表示加密,0表示未加密。
  • LC_LOAD_DYLIB:依赖的动态库,包括动态库路径、当前版本、兼容版本,对应我们依赖framework选择Required
  • LC_LOAD_WEAK_DYLIB:依赖的弱引用库,如果加载路径下有这个库,就引入,否则可以不引入,不影响使用,但是一旦使用的时候没有这个库,也会报异常,对应我们依赖framework是选择Optional
  • LC_RPATH:@math的路径,指定动态链接器搜索路径列表
  • LC_FUNCTION_STARTS:记录函数起始地址表
  • LC_DATA_IN_CODE:定义在代码段内的非指令表。
  • LC_CODE_SIGNATURE:代码签名信息

Load Command 由多个 Segment 构成,同样的我们也可以从loader.h文件中找到Segment的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
  • cmd:segment 的类型,和下面的 flags 标记位决定着这个段如何被装载。
  • cmdsize:section_64 结构体所需要的空间。
  • segname[16]:segment的名字。
  • vmaddr:所描述段的虚拟内存地址。
  • vmsize:为当前段分配的虚拟内存大小。
  • fileoff:当前段在文件中的偏移量。
  • filesize:当前段在文件中占用的字节。
  • maxprot:segment所在页需要的最高内存保护,八进制表示。
  • initprot:segment所在页面原始内存保护。
  • nsects:segment中section的数量
  • flags:标识符

Data

Data 中存放的所有的 Section,例如机器指令,全局变量和局部静态变量,符号表,调试信息等都会被存储到对应的 Section 中。在loader.h文件中可以查看Section结构体的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};

macho-data

几个重要的section如下

__TEXT(代码段)部分:

  • __text:主程序代码。
  • __objc_methname:OC方法名
  • __csstring:只读C语言字符串
  • __objc_methtype:OC方法类型(方法签名),方法签名包含了一个方法的方法名、参数类型、它所在的类等信息。

__Data(数据段)部分:

  • __const:常量
  • __data:存储已经初始化的可变数据
  • __bss:存储未初始化的全局变量和局部静态变量
  • __objc_classname:存储OC的类名。
  • __objc_clalsslist:OC方法列表
  • __objc_protocollist:协议列表
  • _la_symbol_ptr:懒绑定符号指针表 (对应还有一个nl_symbol_pt:非 懒绑定 的指针表)
  • _objc_nlclslist:实现了+load方法的 Objective-C类列表。
  • _ objc_catalist:OC分类( Category)列表。
  • _objc_ classes:OC类引用列表
  • _objc_protore:OC协议引用列表

总结

本文我们主要看了Mach-O文件的结构主要包括:

  • Header:用于快速确定该文件的CPU类型、文件类型
  • Load Commands:指示加载器如何设置并加载二进制数据
  • Data:存放数据,例如代码、数据、字符串常量、类、方法等。

我们研究Mach-O文件有一些应用(后面有机会再详细写),包括但不限于以下几点:

  • 研究fishhook原理,它利用了_la_symbol_ptr等方面原理,在加载时实现C语言方法的替换。
  • 包体积优化,查找未使用的类(_objc_classlist和_objc_ classes比较)
  • load方法使用的检测(_objc_nlclslist)。

我们都知道iOS的编译器是LLVM,本篇我们就探索llvm的编译流程。

解释型语言和编译型语言区别

解释型语言

解释型语言的特征是:它的执行机制是使用一个解释器来执行,解释器对程序一句一句翻译机器语言来一句一句执行。例如:shellpython等。

比如我们执行一段pythion代码:

1
2
///hello.py
print("hello python")

我们执行上面的代码只需要:

python hello.py

编译型语言

编译型语言的特征是:它的执行机制使用编译器来编译成机器语言,然后就可以直接执行编译后的可执行文件。例如:cjava等。

比如我们执行一段c代码:

1
2
3
4
5
6
///hello.c
#include<stdio.h>
void main(int argc,const char* argv[]){
printf("hello c");
return 0;
}

我们首先要对上面的文件进行编译:

1
clang hello.c

执行clang指令后,会生成一个.out的可执行文件,然后执行可执行文件就可以打印出hello c

传统编译器的设计

编译器是由三部分构成的编译器前端优化器编译器后端

传统编译流程

编译器前端

编译器前端主要做词法分析,语法分析,语义分析,检查源代码是否存在错误,构建抽象语法树(Abstract Syntax Tree,AST),最后前端会生成中间代码(intermediate representation,IR)。

优化器

优化器主要负责中间代码的优化。改善代码运行时间,例如消除冗余的计算。

编译器后端

编译器后端主要是将中间代码转换成机器码(二进制),并且进行机器相关的代码优化。

llvm架构编译器的设计

Objective C/C/C++使用的编译器前端是Clang,Swift的前端是Swift,后端是LLVM。示例图如下:

LLVM编译器

编译流程

我们使用main.m文件进行测试查看各阶段编译的结果

1
2
3
4
5
6
7
8
#include <stdio.h>
#define CUSTOM_HEIGHT 3
int testAdd(int a,int b){
return a+b+CUSTOM_HEIGHT;
}
int main(int argc, char * argv[]) {
return testAdd(1, 2);
}

通过命令我们可以打印源码的编译阶段

1
clang -ccc-print-phases main.m
1
2
3
4
5
6
7
0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "arm64", {5}, image

其中含义为:

0:输入文件:找到源文件。

1:预处理阶段:处理包括宏的替换,头文件的导入。

2:编译阶段:进行词法分析、语法分析、检查语法是否正确,最终生成IR.

3:后端:LLVM会通过一个一个的Pass去优化,每个Pass做一些事情,最终生成汇编代码。

4:生成目标文件。

5:链接:链接需要的动态库和静态库,生成可执行文件。

6:通过不同的架构,生成对应的可执行文件。

预处理阶段

执行下面命令

1
clang -E main.m

执行完成后会看到头文件的导入和宏的替换。

编译阶段

词法分析

预处理完成后会进行词法分析。代码会被切成一个个Token,比如大小括号、等于号、字符串等。

1
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

截屏2021-10-03 15.39.32

语法分析

词法分析后就是语法分析,它的任务是验证语法是否正确。再次发分析的基础上将单词序列组合成各类语法短语,如”程序”,”语句”,”表达式”等,然后将所有节点组成抽象语法树(AST),语法分析阶段程序判断源程序在结构上是否正确。

1
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

截屏2021-10-03 15.42.36

生成中间代码IR(intermediate representation)

完成上面的步骤后就开始生成中间代码IR了,代码生成器(Code Gemeration)会将语法树自定向下便利逐步翻译成LLVM IR。通过下面命令,查看IR代码

1
clang -S -fobjc-arc -emit-llvm main.m

Objective C代码这一步会进行runtime的桥接,property合成,ARC处理等。

这一步会生成main.ll文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
target triple = "arm64-apple-macosx11.0.0"

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @testAdd(i32 %0, i32 %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6
%8 = add nsw i32 %7, 3
ret i32 %8
}

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 %0, i8** %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%6 = call i32 @testAdd(i32 1, i32 2)
ret i32 %6
}
attributes #0 = { noinline nounwind optnone ssp uwtable "disable-tail-calls"="false" "frame-pointer"="non-leaf" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "probe-stack"="__chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.5a,+zcm,+zcz" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10, !11}
!llvm.ident = !{!12}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 11, i32 3]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 1, !"Objective-C Garbage Collection", i8 0}
!5 = !{i32 1, !"Objective-C Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 1, !"branch-target-enforcement", i32 0}
!8 = !{i32 1, !"sign-return-address", i32 0}
!9 = !{i32 1, !"sign-return-address-all", i32 0}
!10 = !{i32 1, !"sign-return-address-with-bkey", i32 0}
!11 = !{i32 7, !"PIC Level", i32 2}
!12 = !{!"Apple clang version 13.0.0 (clang-1300.0.29.3)"}

IR的基本语法

@全局标识

%局部标识

alloca开辟空间

align内存对齐

i32 32bit,4字节

store 写入内存

load 读取数据

call 调用函数

ret 返回

IR的优化

LLVM的优化分别是-O0 -O1 -O2 -O3 -Os

1
clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

可以看到优化后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
target triple = "arm64-apple-macosx11.0.0"

; Function Attrs: norecurse nounwind optsize readnone ssp uwtable willreturn
define i32 @testAdd(i32 %0, i32 %1) local_unnamed_addr #0 {
%3 = add i32 %0, 3
%4 = add i32 %3, %1
ret i32 %4
}

; Function Attrs: norecurse nounwind optsize readnone ssp uwtable willreturn
define i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #0 {
ret i32 6
}

attributes #0 = { norecurse nounwind optsize readnone ssp uwtable willreturn "disable-tail-calls"="false" "frame-pointer"="non-leaf" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "probe-stack"="__chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.5a,+zcm,+zcz" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10, !11}
!llvm.ident = !{!12}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 11, i32 3]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 1, !"Objective-C Garbage Collection", i8 0}
!5 = !{i32 1, !"Objective-C Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 1, !"branch-target-enforcement", i32 0}
!8 = !{i32 1, !"sign-return-address", i32 0}
!9 = !{i32 1, !"sign-return-address-all", i32 0}
!10 = !{i32 1, !"sign-return-address-with-bkey", i32 0}
!11 = !{i32 7, !"PIC Level", i32 2}
!12 = !{!"Apple clang version 13.0.0 (clang-1300.0.29.3)"}

bitCode(非必须)

生成.bc的中间代码。

1
clang -emit-llvm -c main.ll -o main.bc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
dec0 170b 0000 0000 1400 0000 0c0b 0000
ffff ffff 4243 c0de 3514 0000 0700 0000
620c 3024 9596 a6a5 f7d7 7f7d d3b4 4ffb
76ef df3f 2d44 0132 0500 0000 210c 0000
7c02 0000 0b02 2100 0200 0000 1600 0000
0781 2391 41c8 0449 0610 3239 9201 840c
2505 0819 1e04 8b62 8010 4502 4292 0b42
8410 3214 3808 184b 0a32 4288 4870 c421
2344 1287 8c10 4192 0264 c808 b114 2043
4688 20c9 0132 4284 182a 282a 9031 7cb0
5c91 20c4 c800 0000 8920 0000 0e00 0000
3222 0809 2064 8504 1322 a484 0413 22e3
84a1 9014 124c 888c 0b84 844c 1034 7304
6050 0600 a098 2340 6888 ca00 5064 0301
2900 0423 0054 0000 5118 0000 b200 0000
1bde 27f8 ffff ffff 0170 0009 2803 4003
c280 2087 7498 8770 1007 7628 8736 a087
7048 0776 6883 7108 0776 6087 7900 cc21
1cd8 611e ca01 20cc 411e c2a1 1dca a10d
e0e1 1dd2 c11d e8a1 1ce4 0170 8777 7087
3660 8772 0807 7300 0876 2887 7998 8736
8007 7928 8771 4887 7928 8736 3007 7868
8770 2007 c01c c281 1de6 a11c 00a2 1dd2
c11d da80 1dca e11c c281 1dda c01e ca61
1ce8 e11d e4a1 0dee 211d c881 1ed0 0180
0380 7087 7768 8374 7007 7398 8736 3007
7868 8376 0807 7a40 07c0 1cc2 811d e6a1
1c00 c21d dea1 0dd4 a11e da01 1eda 801e
c241 1cd8 a11c e601 3087 7060 8779 2807
8070 8777 6803 7708 0777 9887 3630 0778
6883 7608 077a 4007 c01c c281 1de6 a11c
00c2 1dde a10d e621 1dce c11d ca81 1cda
401f ca41 1ede 611e dac0 1ce0 a10d da21
1ce8 011d 0073 0807 7698 8772 0008 7778
8736 a007 7908 0778 8087 7470 8773 6883
7608 077a 4007 801e e4a1 1eca 0120 e041
1ede 411c caa1 0de6 811e c261 1cd6 01f8
855f 1807 7458 8779 a087 75f8 0572 0807
79b8 8774 7007 8098 077a 0887 7158 8736
8007 7978 077a 2887 71a0 8777 9087 3610
877a 3007 7328 0779 6883 7948 077d 2807
000f 0082 1ec2 411e cea1 1ce8 a10d c601
1eea 0108 0778 8007 7628 8736 6887 3800
087a 0807 7938 8772 a087 3630 8772 0807
7aa8 0779 2887 7900 d620 1cca 611e d860
0dc6 411e c681 0dd6 601c e421 1fe0 811e
de81 0dd6 801c de81 1ee0 411e de81 1cd8
600d cc01 1eda 201c e4a1 1dec 010f d860
0dcc 011e e2c0 0ecc a11d d881 0dd6 c01c
ea81 1dd8 c11c e021 0eec 800d d680 1de6
a11c d860 0ddc a11c dec1 1dd8 600d e421
1ce6 810d d640 1ec6 011e c681 0dd6 401e
c8a1 1dd8 600d e601 1dc2 410e d860 0de6
011d c261 0ed8 600d e6a1 1de8 800d d6c0
1ef0 c00d ea20 1cd8 600d f461 1cda 810d
d640 1fc6 411f 00a2 1edc 611e c2c1 1cca
a10d cc01 1eda a01d c281 1ed0 0130 8770
6087 7928 0780 a887 7928 8736 9887 7730
077a 6803 7360 8777 0807 7a00 cc21 1cd8
611e ca01 0000 0000 4918 0000 0100 0000
1382 0000 13b0 7090 8776 b003 3a68 8370
8007 7860 8772 6883 7608 8771 7887 79c0
8738 8803 3780 0337 8083 0d61 500e 6dd0
0e7a f00e 6d90 0e76 4007 7a60 0774 d006
e910 0772 8007 7a10 0772 8007 6de0 0e73
2007 7a60 0774 d006 b310 0772 8007 1a21
0c69 3000 d2f8 cc90 0a38 0200 0002 0000
0000 0000 0000 8003 1852 1d84 0100 4000
0000 0000 0000 0000 7000 121b 048a 0a0a
0000 6481 0000 0000 0a00 0000 321e 9810
1911 4c90 8c09 2647 c604 4382 22a0 1c4b
5806 8212 1801 a019 0120 28c0 8042 2807
0000 0000 b118 0000 9700 0000 3308 801c
c4e1 1c66 1401 3d88 4338 84c3 8c42 8007
7978 0773 9871 0ce6 000f ed10 0ef4 800e
330c 421e c2c1 1dce a11c 6630 053d 8843
3884 831b cc03 3dc8 433d 8c03 3dcc 788c
7470 077b 0807 7948 8770 7007 7a70 0376
7887 7020 8719 cc11 0eec 900e e130 0f6e
300f e3f0 0ef0 500e 3310 c41d de21 1cd8
211d c261 1e66 3089 3bbc 833b d043 39b4
033c bc83 3c84 033b ccf0 1476 6007 7b68
0737 6887 7268 0737 8087 7090 8770 6007
7628 0776 f805 7678 8777 8087 5f08 8771
1887 7298 8779 9881 2cee f00e eee0 0ef5
c00e ec30 0362 c8a1 1ce4 a11c cca1 1ce4
a11c dc61 1cca 211c c481 1dca 6106 d690
4339 c843 3998 4339 c843 39b8 c338 9443
3888 033b 94c3 2fbc 833c fc82 3bd4 033b
b0c3 0cc7 6987 7058 8772 7083 7468 0778
6087 7418 8774 a087 19ce 530f ee00 0ff2
500e e490 0ee3 400f e120 0eec 500e 3320
281d dcc1 1ec2 411e d221 1cdc 811e dce0
1ce4 e11d ea01 1e66 1851 38b0 433a 9c83
3bcc 5024 7660 077b 6807 3760 8777 7807
7898 514c f490 0ff0 500e 331e 6a1e ca61
1ce8 211d dec1 1d7e 011e e4a1 1ccc 211d
f061 0654 8583 38cc c33b b043 3dd0 4339
fcc2 3ce4 433b 88c3 3bb0 c38c c50a 8779
9887 7718 8774 0807 7a28 0772 9881 5ce3
100e ecc0 0ee5 500e f330 23c1 d241 1ee4
e117 d8e1 1dde 011e 6648 193b b083 3db4
831b 84c3 388c 4339 ccc3 3cb8 c139 c8c3
3bd4 033c cc48 b471 0807 7660 0771 0887
7158 8719 dbc6 0eec 600f ede0 06f0 200f
e530 0fe5 200f f650 0e6e 100e e330 0ee5
300f f3e0 06e9 e00e e450 0ef8 3023 e2ec
611c c281 1dd8 e117 ec21 1de6 211d c421
1dd8 211d e821 1f66 209d 3bbc 433d b803
3994 8339 cc58 bc70 7007 7778 077a 0807
7a48 8777 7007 0000 7920 0000 8600 0000
721e 4820 4388 0c19 0972 3248 2023 818c
9191 d144 a010 2864 3c31 3242 8e90 21a3
e830 ee02 cbe4 796d e071 4a36 5d5d e206
5344 4b20 5665 7273 696f 6e4f 626a 6563
7469 7665 2d43 2056 6572 7369 6f6e 4f62
6a65 6374 6976 652d 4320 496d 6167 6520
496e 666f 2056 6572 7369 6f6e 4f62 6a65
6374 6976 652d 4320 496d 6167 6520 496e
666f 2053 6563 7469 6f6e 5f5f 4441 5441
2c5f 5f6f 626a 635f 696d 6167 6569 6e66
6f2c 7265 6775 6c61 722c 6e6f 5f64 6561
645f 7374 7269 704f 626a 6563 7469 7665
2d43 2047 6172 6261 6765 2043 6f6c 6c65
6374 696f 6e4f 626a 6563 7469 7665 2d43
2043 6c61 7373 2050 726f 7065 7274 6965
7377 6368 6172 5f73 697a 6562 7261 6e63
682d 7461 7267 6574 2d65 6e66 6f72 6365
6d65 6e74 7369 676e 2d72 6574 7572 6e2d
6164 6472 6573 7373 6967 6e2d 7265 7475
726e 2d61 6464 7265 7373 2d61 6c6c 7369
676e 2d72 6574 7572 6e2d 6164 6472 6573
732d 7769 7468 2d62 6b65 7950 4943 204c
6576 656c 4170 706c 6520 636c 616e 6720
7665 7273 696f 6e20 3133 2e30 2e30 2028
636c 616e 672d 3133 3030 2e30 2e32 392e
3329 0000 2308 8030 8290 0c23 0800 3182
0014 2308 8331 8200 1c23 0800 3282 0024
330c 4f00 cd30 44c2 33c3 100d d20c 4344
1433 0c91 31cd 3044 0735 c310 21d5 0c43
9448 330c 9122 cd30 448b 34c3 1031 d20c
83d5 3c33 048e 8c04 2628 2336 36bb 3697
b637 b23a b632 1733 b6b0 b3b9 5118 ebc2
326d e33a ef03 8330 4885 8dcd aecd 258d
accc 8d6e 9440 0c00 a918 0000 1c00 0000
0b0a 7228 8777 8007 7a58 7098 433d b8c3
38b0 4339 d0c3 82e6 1cc6 a10d e841 1ec2
c11d e621 1de8 211d dec1 1d16 34e3 600e
e750 0fe1 200f e440 0fe1 200f e750 0ef4
b080 8107 7928 8770 6007 7678 8771 0807
7a28 0772 5870 9cc3 38b4 013b a483 3d94
c382 031e e841 1ec2 a11e e801 1d00 0000
d110 0000 0600 0000 07cc 3ca4 833b 9c03
3b94 033d a083 3c94 4338 90c3 0100 0000
6120 0000 0e00 0000 1304 412c 1000 0000
0100 0000 0465 0000 3311 0040 8cc2 4c04
0010 a330 6c40 08c5 000c 1b10 0131 0024
c020 0106 0a01 0860 6080 c006 0000 0000
6120 0000 0d00 0000 1304 c14c 0400 10a3
3013 0100 c428 cc44 1400 410a c306 c4a0
0cc0 b001 2114 0330 6c40 0404 018c 1818
0008 8241 f02c cd06 0000 0000 7120 0000
0300 0000 320e 1022 8400 e304 1830 4f00
0000 0000 650c 0000 2500 0000 1203 9428
0100 0000 0200 0000 0b00 0000 0600 0000
4c00 0000 0100 0000 5800 0000 0000 0000
5800 0000 0200 0000 8800 0000 0000 0000
1100 0000 1800 0000 2900 0000 0600 0000
0700 0000 0000 0000 8800 0000 0000 0000
0000 0000 0200 0000 0000 0000 2f00 0000
0800 0000 0000 0000 0700 0000 ffff ffff
0024 0000 3700 0000 0500 0000 0700 0000
0400 0000 ffff ffff 0024 0000 0000 0000
5d0c 0000 1200 0000 1203 947c 0000 0000
7465 7374 4164 646d 6169 6e31 332e 302e
3061 726d 3634 2d61 7070 6c65 2d6d 6163
6f73 7831 312e 302e 306d 6169 6e2e 6d5f
7465 7374 4164 645f 6d61 696e 0000 0000

生成汇编代码

我们通过最终的.bc或.ll代码生成汇编代码

1
2
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s

生成的汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
	.section	__TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 11, 3
.globl _testAdd ; -- Begin function testAdd
.p2align 2
_testAdd: ; @testAdd
.cfi_startproc
; %bb.0:
sub sp, sp, #16 ; =16
.cfi_def_cfa_offset 16
str w0, [sp, #12]
str w1, [sp, #8]
ldr w8, [sp, #12]
ldr w9, [sp, #8]
add w8, w8, w9
add w0, w8, #3 ; =3
add sp, sp, #16 ; =16
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32 ; =32
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16 ; =16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
stur wzr, [x29, #-4]
str w0, [sp, #8]
str x1, [sp]
mov w0, #1
mov w1, #2
bl _testAdd
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #32 ; =32
ret
.cfi_endproc
; -- End function
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64

.subsections_via_symbols

汇编代码也可以优化,参数和IR相同

1
clang -Os -S -fobjc-arc main.m -o main.s

优化后的汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	.section	__TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 11, 3
.globl _testAdd ; -- Begin function testAdd
.p2align 2
_testAdd: ; @testAdd
.cfi_startproc
; %bb.0:
add w8, w0, w1
add w0, w8, #3 ; =3
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
mov w0, #6
ret
.cfi_endproc
; -- End function
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64

.subsections_via_symbols

生成目标文件

目标文件生成,汇编器以汇编代码作为输出,将汇编代码转为机器代码,最后输出目标文件(object file)。

1
clang -fmodules -c main.s -o main.o

.o文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cffa edfe 0c00 0001 0000 0000 0100 0000
0400 0000 b801 0000 0020 0000 0000 0000
1900 0000 3801 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
a000 0000 0000 0000 d801 0000 0000 0000
a000 0000 0000 0000 0700 0000 0700 0000
0300 0000 0000 0000 5f5f 7465 7874 0000
0000 0000 0000 0000 5f5f 5445 5854 0000
0000 0000 0000 0000 0000 0000 0000 0000
5400 0000 0000 0000 d801 0000 0200 0000
7802 0000 0100 0000 0004 0080 0000 0000
0000 0000 0000 0000 5f5f 6f62 6a63 5f69
6d61 6765 696e 666f 5f5f 4441 5441 0000
0000 0000 0000 0000 5400 0000 0000 0000
0800 0000 0000 0000 2c02 0000 0000 0000
0000 0000 0000 0000 0000 0010 0000 0000
0000 0000 0000 0000 5f5f 636f 6d70 6163
745f 756e 7769 6e64 5f5f 4c44 0000 0000
0000 0000 0000 0000 6000 0000 0000 0000
4000 0000 0000 0000 3802 0000 0300 0000
8002 0000 0200 0000 0000 0002 0000 0000
0000 0000 0000 0000 3200 0000 1800 0000
0100 0000 0000 0b00 0003 0b00 0000 0000
0200 0000 1800 0000 9002 0000 0500 0000
e002 0000 2800 0000 0b00 0000 5000 0000
0000 0000 0300 0000 0300 0000 0200 0000
0500 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 ff43 00d1 e00f 00b9
e10b 00b9 e80f 40b9 e90b 40b9 0801 090b
000d 0011 ff43 0091 c003 5fd6 ff83 00d1
fd7b 01a9 fd43 0091 bfc3 1fb8 e00b 00b9
e103 00f9 2000 8052 4100 8052 0000 0094
fd7b 41a9 ff83 0091 c003 5fd6 0000 0000
4000 0000 0000 0000 0000 0000 0000 0000
2400 0000 0010 0002 0000 0000 0000 0000
0000 0000 0000 0000 2400 0000 0000 0000
3000 0000 0000 0004 0000 0000 0000 0000
0000 0000 0000 0000 4400 0000 0400 002d
2000 0000 0100 0006 0000 0000 0100 0006
1c00 0000 0e01 0000 0000 0000 0000 0000
1600 0000 0e02 0000 5400 0000 0000 0000
1000 0000 0e03 0000 6000 0000 0000 0000
0100 0000 0f01 0000 2400 0000 0000 0000
0700 0000 0f01 0000 0000 0000 0000 0000
005f 6d61 696e 005f 7465 7374 4164 6400
6c74 6d70 3200 6c74 6d70 3100 6c74 6d70
3000 0000 0000 0000

生成可执行文件(链接)

链接器把编译产生的.o文件和.dylib/.a文件,生成一个mach-o文件。

1
clang main.o -o main

可执行文件s

总结

本篇我们主要研究了编译器的编译流程,从源代码可执行文件

  • 预处理:处理包括宏的替换,头文件的导入等。
  • 词法分析:根据一些标识符对源文件进行切割,词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量和特殊符号。注意这一步是不会检查代码是否有错误。
  • 语法分析:语法分析器对词法分析后的记号进行语法分析,产生抽象语法树(AST),这一步会检查语法是否正确
  • 生成中间代码(IR):将上面生成的语法树转换成中间代码。中间代码使得编译器可以范围内前端和后端。编译器的前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。
  • 生成汇编代码:将中间代码准换成汇编。
  • 生成目标文件(.o):将汇编语言转换成目标文件。
  • 链接目标文件,生成可执行文件:这一步主要是处理多个库依赖的情况。

block是我们开发经常遇到的一个结构,本篇我们就来探索一下它的结构。

block的分类

block的分类相信大家已经很清楚了,分为全局block、堆block和栈block。我们来个例子看看它们的区别

  • 全局block

    1
    2
    3
    4
    5
    void (^block)(void) = ^{
    };
    NSLog(@"%@",block);
    ///打印结果
    <__NSGlobalBlock__: 0x1072d4100>

    全局block是指不捕获任何外部变量的block,只会使用静态变量和全局变量,存储于内存的全局区

  • 堆block

    1
    2
    3
    4
    5
    6
    7
    int a = 10;
    void (^block)(void) = ^{
    NSLog(@"Cooci - %d",a);
    };
    NSLog(@"%@",block);
    ///打印结果
    <__NSMallocBlock__: 0x60000130c4b0>

    堆block会捕获外部变量,存储于内存的堆区

  • 栈block

    1
    2
    3
    4
    5
    6
    7
    int a = 10;
    void (^__weak block)(void) = ^{
    NSLog(@"Cooci - %d",a);
    };
    NSLog(@"%@",block);
    ///打印结果
    <__NSStackBlock__: 0x7ffee06ac4d8>

    栈block也会捕获外部变量,和堆block的区别是需要加__weak修饰,它存储于内存的栈区

block的循环引用

block引起循环引用的原因

A、B相互持有,所以导致A无法调用dealloc方法给B发送release信号,而B也无法接收到release信号。所以A、B此时都无法释放。如图所示

循环引用

解决循环引用

我们看一段循环引用的代码

1
2
3
4
5
NSString *name = @"JS";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();

这段代码出现了循环引用,因为在block内部使用了外部变量name,导致block持有了self,而self原本是持有block的,所以导致了self和block的相互持有

解决方法:

  • __weak和__strong组合使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef void(^JSBlock)(void);

    @property(nonatomic, copy) JSBlock jslBlock;

    __weak typeof(self) weakSelf = self;
    self.jslBlock = ^(void){
    __strong typeof(weakSelf) strongSelf = weakSelf;
    NSLog(@"%@",weakSelf.name);
    }
    self.jslBlock();

    这是我们最容易想到的方式,使用__weak打破强引用,__strong的作用的方式self提前释放,而block执行的时候因为self已经释放而拿不到值。

  • __block定义一个临时变量指向self。

    1
    2
    3
    4
    5
    6
    7
    8
    __block ViewController *vc = self;
    self.jslBlock = ^(void){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"%@",vc.name);
    vc = nil;//需手动释放
    });
    };
    self.jslBlock();

    这种方式是在方法外部定义一个指向self的变量,block内部捕获临时变量,使用结束后将临时变量置为nil,加__block的原因是需要在block内存对其进行置空操作。

  • block加一个参数,使用参数

    1
    2
    3
    4
    5
    6
    7
    8
    typedef void(^JSBlock)(ViewController *);
    @property(nonatomic, copy) JSBlock jslBlock;
    self.jslBlock = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"%@",vc.name);
    });
    };
    self.jslBlock(self);

Block的底层分析

我们主要通过clang和断点调试的方式分析。

xcrun编译分析

我们首先自定义一个block.c文件

1
2
3
4
5
6
7
8
9
#include "stdio.h"
int main(){
int a = 18;
void(^block)(void) = ^{
printf("js - %d",a);
};
block();
return 0;
}

使用xcrun命令讲block.c编译成block.cppxcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("js - %d",a);
}
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 a = 18;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

我们把类型强转的代码去掉:

1
2
3
4
5
6
int main(){
int a = 18;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block->FuncPtr(block);
return 0;
}

简化之后我们看到block代码块是一个__main_block_impl_0__main_block_impl_0的结构是一个结构体,它的impl也是一个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__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;
}
};
///
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

注意:在结构体内部捕获到了外部变量a,且在结构体内部生成了一个成员变量a与其对应。

我们对代码做一下修改:

1
2
3
4
5
6
7
8
9
int main(){
__block int a = 18;
void(^block)(void) = ^{
a++;
printf("js - %d",a);
};
block();
return 0;
}

重新xcrun一下看看结果:

1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
  • 可以看到,现在的a和之前的a不同的是加了__Block_byref_a_0 *修饰,这样就可以对捕获到的变量进行修改,传给block是a的地址,所以block内部可以修改。

  • impl.isa = &_NSConcreteStackBlock说明现在栈类型根据我们前面的分析这里应该是堆block,为什么不同呢。

  • fp是一个函数式保存,如果不调用不会执行。

源码分析

我们首先用汇编,查看源码在哪个库中,我们打断点

断点

然后看汇编代码,源码定位

我们添加符号断点objc_retainBlock:

1629984483869

所以我们去libobjc去搜索objc_retainBlock

1
2
3
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}

实际调用了_Block_copy,在libobjc库中并没有找到方法的实现,我们继续打符号断点:

1629984996709

_Block_copy函数的实现在libsystem库中,这个库没有开源,我们找一个替换的库libclosure的源码分析。我们在libclosure源码中搜索_Block_copy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {// 栈 - 堆 运行时拷贝到堆上
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);

result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}

其中Block_layout结构体的结构是:

1
2
3
4
5
6
7
8
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa;//isa 标识是栈、堆block类型
volatile int32_t flags; // contains ref count 引用计数
int32_t reserved;//流程数据
BlockInvokeFunction invoke;//调用函数
struct Block_descriptor_1 *descriptor;//相关描述
// imported variables
};

对结构有个简单了解之后,我们打符号断点,看运行中block的结构。

  • objc_retainBlock

    1629986289286

    发现此时的block类型还是StackBlock

  • _Block_copy最后打一个断点:

    1629986567227

    此时block的类型就是__NSMallocBlock__类型了。

blockLayout结构
1
2
3
4
5
6
7
8
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa;//isa 标识是栈、堆block类型
volatile int32_t flags; // contains ref count 引用计数
int32_t reserved;//流程数据
BlockInvokeFunction invoke;//调用函数
struct Block_descriptor_1 *descriptor;//相关描述
// imported variables
};

我们看Block_descriptor_1的结构,发现并没有上面调试打印的signature信息。

我们看源码发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷贝函数指针
BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;//签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 布局
}

Block_descriptor_2Block_descriptor_3是可选的,它们是通过Block_descriptor_1内存平移得到的。

捕获变量的copy
_Block_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 栈Block -> 堆Block
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;

if (!arg) return NULL;

// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;//强转为Block_layout类型对象,防止对外界造成影响
if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要释放
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block,直接返回
return aBlock;
}
else {//为栈block 或者 堆block,由于堆区需要申请内存,所以只可能是栈区
// Its a stack block. Make a copy. 它是一个堆栈块block,拷贝。
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);//申请空间并接收
if (!result) return NULL;
//通过memmove内存拷贝,将 aBlock 拷贝至result
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;//可以直接调起invoke
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed 告知可释放
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;//设置block对象类型为堆区block
return result;
}
}

_Block_copy主要是将block从栈区拷贝到堆区

  • 如果需要释放,则直接释放
  • 如果是globalBlock不需要copy,返回
  • 剩下两种情况:堆区block和栈区block。由于堆区block需要申请内存,这里到这里只能是栈block。
    • 通过malloc申请内存空间用于接收block
    • 通过remove将block拷贝至新申请的内存中
    • 设置block对象的类型为堆区block。将isa指向__NSConcreteMallocBlock
_Block_object_assign

先看一个枚举的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Block 捕获的外界变量的种类
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通对象,即没有其他的引用类型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block类型作为变量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//经过__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用变量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};

其中用的最多的是BLOCK_FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static struct Block_byref *_Block_byref_copy(const void *arg) {
//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;

if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}

_Block_object_assign是在底层编译代码中,外部变量拷贝时调用的方法就是它。

  • 如果是普通对象,交给系统arc处理,拷贝对象指针,引用技术+1,外界变量不能释放。
  • 如果是block类型的变量,通过_Block_copy操作,将block从栈区拷贝到堆区。
  • 如果是__block修饰的变量,调用_Block_byref_copy函数,进行内存拷贝以及常规处理。
_Block_byref_copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static struct Block_byref *_Block_byref_copy(const void *arg) {
//强转为Block_byref结构体类型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;

if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申请内存
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力
//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
//等价于 __Block_byref_id_object_copy
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
  • 将传入的对象,强转为Block_byref结构体类型对象。保存
  • 如果没有将变量拷贝到堆上,就申请内存进行拷贝
  • 如果已经拷贝,则进行处理并返回
  • 其中copy和src的forwarding指针都是指向同一片内存。这就是为什么__block修饰的对象具有修改的能力。

三层copy小结

  • 第一层:通过_Block_copy实现对象的自身拷贝,从栈区拷贝至堆区
  • 第二层:通过_Block_byref_copy方法,将对象拷贝为Block_byref结构体类型
  • 第三次:调用_Block_object_assign方法,对__block修饰的当前变量的拷贝

前面几篇我们探索了iOS使用频率很高的多线程技术GCD,本篇我们探索多线程中一个重要的概念

锁的分类

锁主要分为两大类自旋锁互斥锁

自旋锁

在自旋锁中,线程会反复检查变量是否可用。由于线程这个过程中一致保持执行,所以是一种忙等待。 一旦获取了自旋锁,线程就会一直保持该锁,直到显式释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合有效的。对于iOS属性的修饰符atomic,自带一把自旋锁

互斥锁

互斥锁是一种用于多线程编程中,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制,该目的是通过将代码切成一个个临界区而达成。

读写锁

读写锁实际是一种特殊的互斥锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源 进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU 数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。在读写锁保持期间也是抢占失效的。

如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里, 直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。

当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到所有的线程释放锁.通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁⻓期占用, 而等待的写模式锁请求⻓期阻塞.读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁.

几种锁的性能对比

我们通过代码打印的方式比较各种锁性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
int js_runTimes = 100000;
/** OSSpinLock 性能 */
{
OSSpinLock js_spinlock = OS_SPINLOCK_INIT;
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
OSSpinLockLock(&js_spinlock); //解锁
OSSpinLockUnlock(&js_spinlock);
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"OSSpinLock: %f ms",(js_endTime - js_beginTime)*1000);
}

/** dispatch_semaphore_t 性能 */
{
dispatch_semaphore_t js_sem = dispatch_semaphore_create(1);
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
dispatch_semaphore_wait(js_sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(js_sem);
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"dispatch_semaphore_t: %f ms",(js_endTime - js_beginTime)*1000);
}

/** os_unfair_lock_lock 性能 */
{
os_unfair_lock js_unfairlock = OS_UNFAIR_LOCK_INIT;
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
os_unfair_lock_lock(&js_unfairlock);
os_unfair_lock_unlock(&js_unfairlock);
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"os_unfair_lock_lock: %f ms",(js_endTime - js_beginTime)*1000);
}


/** pthread_mutex_t 性能 */
{
pthread_mutex_t js_metext = PTHREAD_MUTEX_INITIALIZER;

double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
pthread_mutex_lock(&js_metext);
pthread_mutex_unlock(&js_metext);
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"pthread_mutex_t: %f ms",(js_endTime - js_beginTime)*1000);
}


/** NSlock 性能 */
{
NSLock *js_lock = [NSLock new];
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
[js_lock lock];
[js_lock unlock];
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"NSlock: %f ms",(js_endTime - js_beginTime)*1000);
}

/** NSCondition 性能 */
{
NSCondition *js_condition = [NSCondition new];
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
[js_condition lock];
[js_condition unlock];
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"NSCondition: %f ms",(js_endTime - js_beginTime)*1000);
}

/** PTHREAD_MUTEX_RECURSIVE 性能 */
{
pthread_mutex_t js_metext_recurive;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&js_metext_recurive, &attr);

double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
pthread_mutex_lock(&js_metext_recurive);
pthread_mutex_unlock(&js_metext_recurive);
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(js_endTime - js_beginTime)*1000);
}

/** NSRecursiveLock 性能 */
{
NSRecursiveLock *js_recursiveLock = [NSRecursiveLock new];
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
[js_recursiveLock lock];
[js_recursiveLock unlock];
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"NSRecursiveLock: %f ms",(js_endTime - js_beginTime)*1000);
}


/** NSConditionLock 性能 */
{
NSConditionLock *js_conditionLock = [NSConditionLock new];
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
[js_conditionLock lock];
[js_conditionLock unlock];
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"NSConditionLock: %f ms",(js_endTime - js_beginTime)*1000);
}

/** @synchronized 性能 */
{
double_t js_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < js_runTimes; i++) {
@synchronized(self) {}
}
double_t js_endTime = CFAbsoluteTimeGetCurrent() ;
JSLog(@"@synchronized: %f ms",(js_endTime - js_beginTime)*1000);
}

在iPhone 12pro模拟器打印的结果为:

模拟器锁性能

在iPhone12 mini真机的结果如下:

12mini锁的性能

可以看到模拟器上@synchronized锁性能是比较差的,但12系列(xr经过测试并没提高)手机的性能有很大提升,我们项目中会比较常见,我们就从@synchronized开始探索。

@synchronized

xcrun分析

我们在main.m文件里写一个锁:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {

}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

使用xcrun命令将其编译成.cpp文件

xcrun -sdk iphoneos clang -arch arm64e -rewrite-objc main.m

main.cpp文件最下方找到main函数的实习,定位到@synchronized代码块

xcrunsyn

将代码排版之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ 
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
}
_sync_exit(_sync_obj);
} catch (id e) {_rethrow = e;}
{
struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}

加锁成功的情况我们只需要关注try代码块及以上的代码。经过我们的简化后的代码:

1
2
3
id _sync_obj = (id)appDelegateClassName; 
objc_sync_enter(_sync_obj);
objc_sync_exit(sync_exit);

可以看到主要就是执行了两个函数objc_sync_enterobjc_sync_exit

libobjc源码分析

通过打符号断点objc_sync_enter,我们可以知道objc_sync_enterlibobjc源码中。

libobjc_enter

接下来我们在libobjc源码中全局搜索objc_sync_enter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;

if (obj) {///重要代码
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}

return result;
}

如果传入objnil,会什么也不做。主要看if代码块的代码,可以看到加锁是通过data->mutex.lock(),也就是SyncData的实例,所以我们先探究一下SyncData类型的结构:

SyncData
1
2
3
4
5
6
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
  • nextData:链表结构下一个节点
  • object:参照关联对象的结构,哈希表
  • threadCount:使用block的线程数量
  • mutex:递归锁(单用多线程会出现问题)

在初始化SyncData实例的时候使用的是id2data函数,我们接下来探索这个函数。

id2data函数

id2data函数有150+行,我们先隐藏代码块,总览一下结构:

id2data

函数的最开始两行有两个宏,它们的定义如下:

1
2
3
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

sDataLists是一个静态哈希表结构,我们使用lldb查看它的数据结构:

sData

经过断点调试,第一次进来执行的代码是:

1
2
3
4
5
6
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;//头插法 添加近链表
*listp = result;

这里有个细节就是listp使用的头插法将新的元素添加到链表,它的可递归性的实现依赖了这种数据结构的使用。

小结

  • synchronized的数据结构是哈希表,采用的拉链法处理哈希冲突
  • sDataLists arrary key是和对象相关的,拉链链表里的元素是同一个对象的锁。
  • objc_sync_enter和objc_sync_exit是对称的,它是一把递归锁。
  • 会有两种存储结构:tls和catch
  • 第一次访问syncData采用的是头插法链表结构 标记threadCount = 1
  • 后续访问,会判断是不是同一个对象,同一对象lockcount++,不是同一个对象threadCount++1。
  • synchronized是一种可重入、递归的多线程锁,原因
    • tls保障 threadCount 可以有多个线程对这个锁对象加锁
    • lock++会记录总共锁了多少次。

NSLock和NSRecursiveLock使用

我们以一个实例分析这两种锁的区别:

1
2
3
4
5
6
7
8
9
10
11
12
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
});
}

上面这个代码有多线程冲突的问题,打印的结果无序,不是我们想要的结果

没有锁无序

使用NSLock解决问题

使用NSLock解决的方法其实很简单,就是在我们调用方法testMethod前后加锁。

nslock

我们再看打印结果就正常了。

nslock打印结果

NSLock适用的是在最外层加锁,如果我们能写的代码只能在testMethod操作,这个时候加NSLock就不会正常工作了。

nslock加在业务代码

使用NSRecursiveLock

上面我们知道NSLocktestMethod无法解决问题,我们尝试用NSRecursiveLock解决。

nsrecrusiveLock

发现NSRecursiveLock并不能解决问题,而且还会偶现崩溃。NSRecursiveLock是一把递归锁,但是它并不支持多线程递归。

使用@synchronized

sychorsize解决问题

使用@synchronized解决了业务代码里的问题,说明@synchronized是一把支持多线程的递归锁。

NSCondition

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要 为了当检测条件时保护数据源,执行条件引发的任务;线程检查器 主要是根据条件决定是否继续运行线程,即线程是否被阻塞。它主要有四个方法

  • [condition lock]:一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
  • [condition unlock];//lock 同时使用
  • [condition wait];//让当前线程处于等待状态
  • [condition signal];//CPU发信号告诉线程不用在等待,可以继续执行

它的一个应用场景之一就是生产者-消费者模型。也就是通过多线程进行生产和销售产品,当产品数量为0的时候就只能等待,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (void)js_testConditon{
_testCondition = [[NSCondition alloc] init];
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self js_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self js_consumer];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self js_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self js_producer];
});
}
}
- (void)js_producer{
[_testCondition lock]; // 操作的多线程影响
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_testCondition signal]; // 信号
[_testCondition unlock];
}
- (void)js_consumer{
[_testCondition lock]; // 操作的多线程影响
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}

Foundation源码看锁的封装

我们在swift-corelibs-foundation源码中探索。

通过源码我们看到,NSLock等锁都实现了一个协议就是NSLocking

1
2
3
4
public protocol NSLocking {
func lock()
func unlock()
}

它们都是对pthread的封装

锁的源码

NSRecursiveLock,也类似,它和NSLock的区别是pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))

nsrecursivelock源码

NSConditionLock

  • NSConditionLock 是锁,一旦一个线程获得锁,其他线程一定等待
  • lock函数:表示 对象期待获得锁,如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
  • [xx lockWhenCondition:A条件]方法:表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且 没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的 完成,直至它解锁。
  • [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
  • return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理。

栅栏函数实现读写锁

读写锁主要要实现以下功能:

  • 多读单写功能。
  • 写入和写入互斥。
  • 读和写入互斥。
  • 写入不能阻塞主线程任务执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@interface ViewController ()

@property (nonatomic, strong) dispatch_queue_t js_currentQueue;
@property (nonatomic, strong) NSMutableDictionary *mDict;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.js_currentQueue = dispatch_queue_create("jscurrent", DISPATCH_QUEUE_CONCURRENT);
self.mDict = [[NSMutableDictionary alloc] init];
[self js_safeSetter:@"123" time:10];
[self js_safeSetter:@"456" time:5];
[self js_safeSetter:@"789" time:3];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (int i = 0; i < 5; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"读取,name = %@ thread---%@",[self js_safeGetter],[NSThread currentThread]);
});
}
}

- (void)js_safeSetter:(NSString *)name time:(int)time{
dispatch_barrier_async(self.js_currentQueue, ^{
sleep(time);
[self.mDict setValue:name forKey:@"name"];
NSLog(@"写入,name = %@ thread---%@",name,[NSThread currentThread]);
});

}

- (NSString *)js_safeGetter{
__block NSString *result;
dispatch_sync(self.js_currentQueue, ^{
result = self.mDict[@"name"];
});
return result;
}
@end

程序运行我们就开始点击屏幕(读操作),最后看打印结果:

读写锁

前两篇我们主要探索了GCD的函数和队列的调度及死锁和单例,本篇我们开始探索GCD的其他函数。

栅栏函数

栅栏函数最直接的作用是:控制任务执行顺序,同步

  • dispatch_battier_async 前面的任务执行完毕才会来到这里。
  • dispatch_battier_sync 作用相同,但是会阻塞线程,影响后面的任务执行。
  • 栅栏函数只能控制同一并发队列。

栅栏函数的使用

我们一般会如下使用栅栏函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t concurrentQueue = dispatch_queue_create("jason", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"123");
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"456");
});
dispatch_barrier_async(concurrentQueue1, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"789");
});
NSLog(@"10 11 12");

我们打印结果:

1
2
3
4
5
123
10 11 12
456
----<NSThread: 0x6000012ca180>{number = 2, name = (null)}-----
789

可以看到栅栏函数阻塞了自己的block和后续异步函数的执行,也就是必须前面的函数执行之后才会执行后续操作。

我们换成同步栅栏函数再执行一次:

1
2
3
dispatch_barrier_sync(concurrentQueue1, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});

打印结果:

1
2
3
4
5
123
456
----<NSThread: 0x6000025a4140>{number = 1, name = main}-----
10 11 12
789

可以看到dispatch_battier_sync阻塞了NSLog(@"10 11 12");代码的执行。

注意:还有一个点需要注意就是栅栏函数不能阻塞全局并发队列

接下来我们从源码层面看栅栏函数的实现以及为什么全局并发队列不能被栅栏函数阻塞。

栅栏函数底层原理

我们还是以同步函数dispatch_barrier_sync为例探索

调试代码如图:

1629551309944

调试代码有个点就是sleep(30)方便调试等待前面方法执行完成之前的调用。

我们从libdispatch源码中搜索dispatch_barrier_sync:

1
2
3
4
5
6
7
8
9
void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

调用了_dispatch_barrier_sync_f函数,继续跟进去:

1
2
3
4
5
6
static void
_dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
_dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
}

调用_dispatch_barrier_sync_f_inline函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();

if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}

dispatch_lane_t dl = upcast(dq)._dl;
// The more correct thing to do would be to merge the qos of the thread
// that just acquired the barrier lock into the queue state.
//
// However this is too expensive for the fast path, so skip doing it.
// The chosen tradeoff is that if an enqueue on a lower priority thread
// contends with this fast path, this thread may receive a useless override.
//
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
//经过断点调试执行了这里
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
DC_FLAG_BARRIER | dc_flags);
}

if (unlikely(dl->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}

这里我们加两个符号断点调试一下看看执行的是_dispatch_sync_f_slow还是_dispatch_sync_recurse,经过添加符号断点实际调用了_dispatch_sync_f_slow函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt,
dispatch_function_t func, uintptr_t top_dc_flags,
dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
dispatch_queue_t top_dq = top_dqu._dq;
dispatch_queue_t dq = dqu._dq;
if (unlikely(!dq->do_targetq)) {
return _dispatch_sync_function_invoke(dq, ctxt, func);
}

pthread_priority_t pp = _dispatch_get_priority();
struct dispatch_sync_context_s dsc = {
.dc_flags = DC_FLAG_SYNC_WAITER | dc_flags,
.dc_func = _dispatch_async_and_wait_invoke,
.dc_ctxt = &dsc,
.dc_other = top_dq,
.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
.dc_voucher = _voucher_get(),
.dsc_func = func,
.dsc_ctxt = ctxt,
.dsc_waiter = _dispatch_tid_self(),
};

_dispatch_trace_item_push(top_dq, &dsc);
__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);

if (dsc.dsc_func == NULL) {
// dsc_func being cleared means that the block ran on another thread ie.
// case (2) as listed in _dispatch_async_and_wait_f_slow.
dispatch_queue_t stop_dq = dsc.dc_other;
return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
}
_dispatch_introspection_sync_begin(top_dq);
_dispatch_trace_item_pop(top_dq, &dsc);
_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
DISPATCH_TRACE_ARG(&dsc));
}

_dispatch_sync_f_slow函数我们比较熟悉了,在上一篇探索过,我们继续添加符号断点,

这里有个细节,就是会先调用__DISPATCH_WAIT_FOR_QUEUE__等待sleep(300);的调用结束说明等待的过程是__DISPATCH_WAIT_FOR_QUEUE__处理,接着会调用到dq_push_dispatch_lane_concurrent_push函数->_dispatch_lane_push->_dispatch_lane_push_waiter也就是阻塞了当前队列。

  • 自定义并发队列会执行_dispatch_lane_wakeup会有等待barrier的判断。
  • 全局并发队列会执行_dispatch_root_queue_wakeup方法,所以不会有等待的方法,所以不会阻塞

等待执行的队列完成之后会调用_dispatch_lane_class_barrier_complete函数->dx_wakeup->_dispatch_lane_wakeup->_dispatch_queue_wakeup

这里会调用_dispatch_client_callout函数,然后同步函数就接着被执行了。

信号量

信号量的使用

先看使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // sem开始为0 需要等待等待
NSLog(@"执行任务1");
NSLog(@"任务1完成");
});
//任务2
dispatch_async(queue, ^{
sleep(2);
NSLog(@"执行任务2");
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem); // 发信号 sem+1
});

信号量主要有三个函数:

  • dispatch_semaphore_create:创建信号量
  • dispatch_semaphore_wait:等待信号量
  • dispatch_semaphore_signal:信号量释放

它可以控制GCD的最大并发数。

信号量的底层原理

底层原理其实就是三个方法的探索,我们依次探索

dispatch_semaphore_wait

全局搜索dispatch_semaphore_wait

1
2
3
4
5
6
7
8
9
10
intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
//--1
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}

前面的例子我们创建的信号量为0,经过–操作<0,会执行_dispatch_semaphore_wait_slow函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DISPATCH_NOINLINE
static intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout)
{
long orig;

_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
switch (timeout) {
default:
if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
//超时操作
orig = dsema->dsema_value;
while (orig < 0) {
if (os_atomic_cmpxchgv2o(dsema, dsema_value, orig, orig + 1,
&orig, relaxed)) {
return _DSEMA4_TIMEOUT();
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
//一直等待
_dispatch_sema4_wait(&dsema->dsema_sema);
break;
}
return 0;
}

可见等待的函数是_dispatch_sema4_wait:

1
2
3
4
5
6
7
8
9
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
int ret = 0;
do {
ret = sem_wait(sema);
} while (ret == -1 && errno == EINTR);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}

sem_wait是c语言的方法,这里是一个do-while循环事宜等待信号量的值满足条件。

#####dispatch_semaphore_signal

全局搜索dispatch_semaphore_signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
//++1
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}

这里信号量++一次,如果信号量>0就可以正常执行了,如果信号量还是<=0,会进入函数_dispatch_semaphore_signal_slow,它是异常的处理一直++信号量的值,直到返回的值为正值。

小结

  • dispatch_semaphore_create:初始化信号量。
  • dispatch_semaphore_wait:对信号量的value–
  • dispatch_semaphore_signal对信号量的value++

调度组

####调度组的使用

有时候我们可能需要等待多个接口都返回数据才能进行下一步的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
//下载任务1
sleep(2);
});
dispatch_group_async(group, queue, ^{
//下载任务2
sleep(2);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//组里任务都执行之后,下一步的操作。
});

或者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
//下载任务1
sleep(2);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
//下载任务2
sleep(2);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//组里任务都执行之后,下一步的操作。
});

调度组底层原理

我们从三个方面分析,组是如何控制同步的、dispatch_group_enterdispatch_group_leave的搭配、dispatch_group_async的原理。

dispatch_group_create函数
1
2
3
4
5
dispatch_group_t
dispatch_group_create(void)
{
return _dispatch_group_create_with_count(0);
}

主要是创建了一个组的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
sizeof(struct dispatch_group_s));
dg->do_next = DISPATCH_OBJECT_LISTLESS;
dg->do_targetq = _dispatch_get_default_queue(false);
if (n) {
os_atomic_store2o(dg, dg_bits,
(uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
}
return dg;
}
dispatch_group_enter函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
dispatch_group_enter(dispatch_group_t dg)
{
// The value is decremented on a 32bits wide atomic so that the carry
// for the 0 -> -1 transition is not propagated to the upper 32bits.
/// --操作
uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
if (unlikely(old_value == 0)) {
_dispatch_retain(dg); // <rdar://problem/22318411>
}
if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
DISPATCH_CLIENT_CRASH(old_bits,
"Too many nested calls to dispatch_group_enter()");
}
}

开始count == 0,进行–操作,变为-1,

dispatch_group_leave函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void
dispatch_group_leave(dispatch_group_t dg)
{
// The value is incremented on a 64bits wide atomic so that the carry for
// the -1 -> 0 transition increments the generation atomically.
uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
DISPATCH_GROUP_VALUE_INTERVAL, release);
uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);

if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {//old_state == -1
old_state += DISPATCH_GROUP_VALUE_INTERVAL;
do {
new_state = old_state;
if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
} else {
// If the group was entered again since the atomic_add above,
// we can't clear the waiters bit anymore as we don't know for
// which generation the waiters are for
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
}
if (old_state == new_state) break;
} while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
old_state, new_state, &old_state, relaxed)));
return _dispatch_group_wake(dg, old_state, true);///唤醒 notify
}

if (unlikely(old_value == 0)) {
DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
"Unbalanced call to dispatch_group_leave()");
}
}

count由-1变为0,可以看出old_state != new_state时会一直while循环,当相等是执行_dispatch_group_wake唤醒阻塞的函数。

#####dispatch_group_notify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dsn)
{
uint64_t old_state, new_state;
dispatch_continuation_t prev;

dsn->dc_data = dq;
_dispatch_retain(dq);

prev = os_mpsc_push_update_tail(os_mpsc(dg, dg_notify), dsn, do_next);
if (os_mpsc_push_was_empty(prev)) _dispatch_retain(dg);
os_mpsc_push_update_prev(os_mpsc(dg, dg_notify), prev, dsn, do_next);
if (os_mpsc_push_was_empty(prev)) {
os_atomic_rmw_loop2o(dg, dg_state, old_state, new_state, release, {
new_state = old_state | DISPATCH_GROUP_HAS_NOTIFS;
if ((uint32_t)old_state == 0) {
os_atomic_rmw_loop_give_up({/// block callout函数
return _dispatch_group_wake(dg, new_state, false);
});
}
});
}
}

可以看到old_state == 0的时候才会执行block,也就是dispatch_group_leave执行完成。

dispatch_group_async
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_block_t db)
{
//任务封装
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
_dispatch_continuation_group_async(dg, dq, dc, qos);
}
static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dc, dispatch_qos_t qos)
{
dispatch_group_enter(dg);//执行了enter
dc->dc_data = dg;
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);//调用的函数
}

这里在_dispatch_continuation_group_async函数调用了dispatch_group_enter函数,然后再dx_push之后也就是callout函数之后执行了dispatch_group_leave函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_with_group_invoke(dispatch_continuation_t dc)
{
struct dispatch_object_s *dou = dc->dc_data;
unsigned long type = dx_type(dou);
if (type == DISPATCH_GROUP_TYPE) {
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
_dispatch_trace_item_complete(dc);
///执行了leave方法
dispatch_group_leave((dispatch_group_t)dou);
} else {
DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
}
}

小结

  • dispatch_group_enterdispatch_group_leave成对出现
  • dispatch_group_enter底层对group的value做–操作(0->1)
  • dispatch_group_leave底层对group的value做++操作(-1->0)
  • dispatch_group_notify底层判断group的state是否为0,为0就通知执行block。
  • 任务唤醒有两种方式:1、dispatch_group_leave2、dispatch_group_notify
  • dispatch_group_async等同于 enter+leave,底层实现包含一对enter+leave。

###dispatch_source

dispatch_source我们平时会使用到的场景是一个计时器(倒计时):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (void)timeDone{
//倒计时时间
__block int timeout = 3;

//创建队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

//创建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);

//设置1s触发一次,0s的误差
/*
- source 分派源
- start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
- interval 间隔时间
- leeway 计时器触发的精准程度
*/
dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);

//触发的事件
dispatch_source_set_event_handler(timer, ^{
//倒计时结束,关闭
if (timeout <= 0) {
//取消dispatch源
dispatch_source_cancel(timer);
}else{
timeout--;

dispatch_async(dispatch_get_main_queue(), ^{
//更新主界面的操作
NSLog(@"倒计时 - %d", timeout);
});
}
});
//开始执行dispatch源
dispatch_resume(timer);
}

上一篇我们主要探索了GCD的主队列及串行队列并发队列在源码上的区分,以及同步函数的调用时机。本节我们主要探索同步函数与异步函数的区别:

  • 同步函数死锁分析
  • 任务回调是否具有同步性、异步性
  • dispatch_once底层的分析

我们先从同步函数开始探索。

同步函数死锁分析

libdispatch源码中全局搜索dispatch_sync,找到方法的实现

1
2
3
4
5
6
7
8
9
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

继续跟进方法_dispatch_sync_f实际调用_dispatch_sync_f_inline方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {//串行队列
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}

if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}

dispatch_lane_t dl = upcast(dq)._dl;
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
}

if (unlikely(dq->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}

串行队列调用了_dispatch_barrier_sync_f方法,最终调用到_dispatch_barrier_sync_f_inline方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
dispatch_tid tid = _dispatch_tid_self();

if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}

dispatch_lane_t dl = upcast(dq)._dl;
if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
//死锁的是否会有_dispatch_sync_f_slow异常
return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
DC_FLAG_BARRIER | dc_flags);
}

if (unlikely(dl->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func,
DC_FLAG_BARRIER | dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}

这里有多个调用的方法,我们自定义一种死锁的情况看调用堆栈的情况:

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t queue = dispatch_queue_create("jason", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");

执行代码查看调用栈:

死锁

可以看到调用了_dispatch_sync_f_slow方法,然后调用了__DISPATCH_WAIT_FOR_QUEUE__

1
2
3
4
5
6
7
8
9
10
11
12
DISPATCH_NOINLINE
static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq)
{
uint64_t dq_state = _dispatch_wait_prepare(dq);
if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
"dispatch_sync called on queue "
"already owned by current thread");
}
///省略代码
}

可以看到这里的crash信息和我们写的例子中最终的错误是一致的

死锁2

说明_dq_state_drain_locked_by判断的条件是产生死锁的原因,这个函数调用了_dispatch_lock_is_locked_by函数:

1
2
3
4
5
6
7
8
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
// equivalent to _dispatch_lock_owner(lock_value) == tid
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
#define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc)

DLOCK_OWNER_MASK是一个很大的值,说明lock_value ^ tid为0,也就是tid=lock_value,看上面的注释也是这个意思,即当前的等待的线程与现在执行的线程是同一个。

同步函数的回调

我们建立一个全局并发队列探索

1
2
3
4
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
dispatch_sync(concurrentQueue, ^{
NSLog(@"函数调用了....");
});

我们回到_dispatch_sync_f_inline函数,然后打符号断点看执行了哪个方法

  • _dispatch_sync_f_slow
  • _dispatch_sync_recurse
  • _dispatch_introspection_sync_begin
  • _dispatch_sync_invoke_and_complete

同步函数并发队列探索

断点调用到了_dispatch_sync_f_slow函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt,
dispatch_function_t func, uintptr_t top_dc_flags,
dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
dispatch_queue_t top_dq = top_dqu._dq;
dispatch_queue_t dq = dqu._dq;
if (unlikely(!dq->do_targetq)) {
return _dispatch_sync_function_invoke(dq, ctxt, func);
}

pthread_priority_t pp = _dispatch_get_priority();
struct dispatch_sync_context_s dsc = {
.dc_flags = DC_FLAG_SYNC_WAITER | dc_flags,
.dc_func = _dispatch_async_and_wait_invoke,
.dc_ctxt = &dsc,
.dc_other = top_dq,
.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
.dc_voucher = _voucher_get(),
.dsc_func = func,
.dsc_ctxt = ctxt,
.dsc_waiter = _dispatch_tid_self(),
};

_dispatch_trace_item_push(top_dq, &dsc);
__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);

if (dsc.dsc_func == NULL) {
// dsc_func being cleared means that the block ran on another thread ie.
// case (2) as listed in _dispatch_async_and_wait_f_slow.
dispatch_queue_t stop_dq = dsc.dc_other;
return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
}
_dispatch_introspection_sync_begin(top_dq);
_dispatch_trace_item_pop(top_dq, &dsc);
_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
DISPATCH_TRACE_ARG(&dsc));
}

我们继续加符号断点跟踪_dispatch_trace_item_push_dispatch_sync_complete_recurse_dispatch_trace_item_pop_dispatch_sync_invoke_and_complete_recurse_dispatch_sync_function_invoke

同步函数并发队列

说明执行了_dispatch_sync_function_invoke函数,注意dq->do_targetq系统队列为空,因为我们使用的是全局并发队列,所以执行到了这里。

继续看_dispatch_sync_function_invoke函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DISPATCH_NOINLINE
static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}
static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
dispatch_thread_frame_s dtf;
_dispatch_thread_frame_push(&dtf, dq);
_dispatch_client_callout(ctxt, func);
_dispatch_perfmon_workitem_inc();
_dispatch_thread_frame_pop(&dtf);
}

可以看到这里执行了_dispatch_client_callout函数,也就调用了回调函数

异步函数回调

同样,我们也用符号断点的方式探究:

1
2
3
4
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
dispatch_async(concurrentQueue, ^{
NSLog(@"函数调用了....");
});

全局搜索dispatch_async其调用了_dispatch_continuation_async函数,然后调用dx_push

1
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

最后调用了dq_push,dq_push根据队列类型的不同而调用,我们看并发队列的

1
2
3
4
5
6
7
8
9
10
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
.do_type = DISPATCH_QUEUE_CONCURRENT_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,

.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_concurrent_push,
);

搜索_dispatch_lane_concurrent_push的实现实际调用了_dispatch_lane_push:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void
_dispatch_lane_push(dispatch_lane_t dq, dispatch_object_t dou,
dispatch_qos_t qos)
{
dispatch_wakeup_flags_t flags = 0;
struct dispatch_object_s *prev;

if (unlikely(_dispatch_object_is_waiter(dou))) {
return _dispatch_lane_push_waiter(dq, dou._dsc, qos);
}
dispatch_assert(!_dispatch_object_is_global(dq));
qos = _dispatch_queue_push_qos(dq, qos);

prev = os_mpsc_push_update_tail(os_mpsc(dq, dq_items), dou._do, do_next);
if (unlikely(os_mpsc_push_was_empty(prev))) {
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2 | DISPATCH_WAKEUP_MAKE_DIRTY;
} else if (unlikely(_dispatch_queue_need_override(dq, qos))) {
_dispatch_retain_2_unsafe(dq);
flags = DISPATCH_WAKEUP_CONSUME_2;
}
os_mpsc_push_update_prev(os_mpsc(dq, dq_items), prev, dou._do, do_next);
if (flags) {
return dx_wakeup(dq, qos, flags);
}
}

我们添加符号断点_dispatch_lane_push_waiter_dispatch_queue_push_qosos_mpsc_push_update_prevdx_wakeup。调用的是dx_wakeup:

1
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

实际是对dq_wakeup的封装,依然我们找并发队列:

1
2
3
4
5
6
7
8
9
10
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
.do_type = DISPATCH_QUEUE_CONCURRENT_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,

.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_concurrent_push,
);

_dispatch_lane_wakeup调用的_dispatch_queue_wakeup,同样也是把_dispatch_queue_wakeup调用的return的方法添加符号断点,会调用_dispatch_queue_wakeup->_dispatch_lane_push,执行到_dispatch_root_queue_drain

dispatch_once函数底层实现

我们平时定义一个单例对象的时候一般都会使用dispatch_once函数

1
2
3
4
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"once")
});

我们在libdispatch中搜索dispatch_once看其底层实现:

1
2
3
4
5
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}

valdispatch_once_t类型。继续跟进dispatch_once_f函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
//gate
dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
if (likely(v == DLOCK_ONCE_DONE)) {//第一次会标示为done return
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
///第一次调用
if (_dispatch_once_gate_tryenter(l)) {//锁 说明单例是线程安全的
return _dispatch_once_callout(l, ctxt, func);//执行任务
}
return _dispatch_once_wait(l);
}
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
///执行任务
_dispatch_client_callout(ctxt, func);
///广播关门处理
_dispatch_once_gate_broadcast(l);
}
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l);
#else
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}

小结

  • 只执行一次原理:onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量ll主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接`return

  • block调用的时机:如果此时任务没有执行过,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行,会直接返回

  • 如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的

上一篇我们介绍了多线程的一些概念,本篇我们主要探究iOS开发中经常会使用到的多线程技术GCD

GCD的概念

GCD的全称是 Grand Central Dispatch。它是由纯 C 语言实现,提供了非常多强大的函数。它有如下优势:

  • GCD 是苹果公司为多核的并行运算提出的解决方案
  • GCD 会自动利用更多的CPU内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

总结来说GCD就是将任务添加到队列,并指定任务执行的函数。

GCD的基本使用

一般情况下我们会这样使用GCD

1
2
3
4
5
6
7
8
//创建任务block
dispatch_block_t block = ^{
NSLog(@"这是任务");
};
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
//执行任务
dispatch_async(queue, block);

总结来看就是三部:

  • 创建任务块dispatch_block_t
  • 创建队列dispatch_queue_t
  • 将任务添加到队列并执行任务函数dispatch_asyncdispatch_sync

还有两个概念其实我们也很熟悉了就是函数队列

  • 函数包括同步函数(dispatch_sync)异步函数(dispatch_async)
  • 队列包括串行队列(DISPATCH_QUEUE_SERIAL)并行队列(DISPATCH_QUEUE_CONCURRENT)

主队列

主队列(dispatch_queue_main_t)是我们运行程序就会启动的一个队列,它是主线程所在的队列,会贯穿我们应用运行的始终。通过我们dispatch_get_main_queue函数的注释我们看到主队列是一个串行队列,这也不难理解,因为串行队列里的任务会逐个顺序执行,而我们主线程上的任务也符合这一特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//下面这行注释说明它是一个串行队列,但不完全是一个标准的串行队列。  
* Because the main queue doesn't behave entirely like a regular serial queue,
* it may have unwanted side-effects when used in processes that are not UI apps
* (daemons). For such processes, the main queue should be avoided.
*
* @see dispatch_queue_main_t
*
* @result
* Returns the main queue. This queue is created automatically on behalf of
* the main thread before main() is called.
*/
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

我们下载libdispatch的源码,看一下dispatch_get_main_queue的源码,调用的是DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q),它是一个

1
#define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast<type>(&(object)))

可以看到第一个参数type是类型,第二个参数object参数真正的参数也就是_dispatch_main_q,我们全局搜索_dispatch_main_q =

1
2
3
4
5
6
7
8
9
10
11
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};

发现_dispatch_main_q是一个结构体。可以看到dispatch_queue_main_t是一个结构体dispatch_queue_static_s

串行队列和并发队列源码上的区分

上面我们已经知道,gcd的队列的本质是dispatch_queue_static_s结构体,结构体中那个成员标示的是串行还是并行队列呢?我们源码中找答案。我们的队列是通过dispatch_queue_create函数创建的,它的第二个参数传入的是队列的类型,我们源码中找dispatch_queue_create函数的定义:

1
2
3
4
5
6
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

跟着调用继续查找_dispatch_lane_create_with_target函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

//
// Step 1: Normalize arguments (qos, overcommit, tq)
//

dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
}
if (qos == DISPATCH_QOS_MAINTENANCE) {
dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
if (tq->do_targetq) {
DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
"a non-global target queue");
}
}

if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
// Handle discrepancies between attr and target queue, attributes win
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
overcommit = _dispatch_queue_attr_overcommit_enabled;
} else {
overcommit = _dispatch_queue_attr_overcommit_disabled;
}
}
if (qos == DISPATCH_QOS_UNSPECIFIED) {
qos = _dispatch_priority_qos(tq->dq_priority);
}
tq = NULL;
} else if (tq && !tq->do_targetq) {
// target is a pthread or runloop root queue, setting QoS or overcommit
// is disallowed
if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
"and use this kind of target queue");
}
} else {
if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
// Serial queues default to overcommit!
overcommit = dqai.dqai_concurrent ?
_dispatch_queue_attr_overcommit_disabled :
_dispatch_queue_attr_overcommit_enabled;
}
}
if (!tq) {
tq = _dispatch_get_root_queue(
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
if (unlikely(!tq)) {
DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
}
}

//
// Step 2: Initialize the queue
//

if (legacy) {
// if any of these attributes is specified, use non legacy classes
if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
legacy = false;
}
}

const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
switch (dqai.dqai_autorelease_frequency) {
case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
dqf |= DQF_AUTORELEASE_NEVER;
break;
case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
dqf |= DQF_AUTORELEASE_ALWAYS;
break;
}
if (label) {
const char *tmp = _dispatch_strdup_if_mutable(label);
if (tmp != label) {
dqf |= DQF_LABEL_NEEDS_FREE;
label = tmp;
}
}

dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

dq->dq_label = label;
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
_dispatch_queue_priority_inherit_from_target(dq, tq);
_dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
}

这个函数比较长,按照管理我们还是先看返回值_dispatch_trace_queue_create(dq)._dq。重点看dq怎么创建的。所以我们主要dq的创建及成员赋值的过程。

1
2
3
4
5
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

发现_dispatch_queue_init的实参有dqai.dqai_concurrent还是并行队列的判断。我们定位到_dispatch_queue_init的第三个参数,看看其赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
uint16_t width, uint64_t initial_state_bits)
{
uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
dispatch_queue_t dq = dqu._dq;

dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
DISPATCH_QUEUE_INACTIVE)) == 0);

if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
dq->do_ref_cnt++; // released when DSF_DELETED is set
}
}

dq_state |= initial_state_bits;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dqf |= DQF_WIDTH(width);
os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
dq->dq_state = dq_state;
dq->dq_serialnum =
os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
return dqu;
}

看到第三个参数width复制的地方是dqf |= DQF_WIDTH(width);即:

  • width = 1表示串行队列

  • width = DISPATCH_QUEUE_WIDTH_MAX表示并行队列,其中DISPATCH_QUEUE_WIDTH_MAX的定义如下。

    1
    2
    #define DISPATCH_QUEUE_WIDTH_FULL			0x1000ull
    #define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)

    dispatch_queue_t的继承链

dispatch_queue_t的继承链是什么样子呢,我们在代码中按cmd+dispatch_queue_t会跳转到DISPATCH_DECL(dispatch_queue);代码,它是dispatch_queue_t的定义。我们在libdispatch源码中搜索DISPATCH_DECL(找定义的地方,根据上下文if判断下面这行是oc情况下的定义:

1
2
3
#define DISPATCH_DECL(name) \
typedef struct name##_s : public dispatch_object_s {} *name##_t
///dispatch_queue_t -> dispatch_queue_s -> dispatch_object_s

可以看到继承链为dispatch_queue_t -> dispatch_queue_s -> dispatch_object_s

我们观察一下的机构

1
2
3
4
struct dispatch_queue_s {
DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
/* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;

继续看DISPATCH_QUEUE_CLASS_HEADER结构体

1
2
3
4
5
6
7
8
#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
DISPATCH_OBJECT_HEADER(x); \
__pointer_sized_field__; \
DISPATCH_UNION_LE(uint64_t volatile dq_state, \
dispatch_lock dq_state_lock, \
uint32_t dq_state_bits \
)
#endif

继承于DISPATCH_OBJECT_HEADER继续搜索:

1
2
3
4
5
6
7
8
9
10
#define _DISPATCH_OBJECT_HEADER(x) \
struct _os_object_s _as_os_obj[0]; \
OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
struct dispatch_##x##_s *volatile do_next; \
struct dispatch_queue_s *do_targetq; \
void *do_ctxt; \
union { \
dispatch_function_t DISPATCH_FUNCTION_POINTER do_finalizer; \
void *do_introspection_ctxt; \
}

最后继承的是_os_object_s,所以完整继承链就是

dispatch_queue_t -> dispatch_queue_s -> dispatch_object_s -> _os_object_s

函数的调用时机

1
2
3
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"函数调用了");
});

我们本小节探究函数的block参数是什么时候调用的,我们以同步函数为例,全局搜索dispatch_sync

1
2
3
4
5
6
7
8
9
10
DISPATCH_NOINLINE
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

work为我们传入的block,所以我们看和work参数相关的代码

_dispatch_Block_invoke函数的实现:

1
2
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)

可以看到_dispatch_Block_invoke函数主要是调用了workinvoke方法。

我们再看_dispatch_sync_f的实现:

1
2
3
4
5
6
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
uintptr_t dc_flags)
{
_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}

继续跟踪_dispatch_sync_f_inline函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
}
dispatch_lane_t dl = upcast(dq)._dl;
// Global concurrent queues and queues bound to non-dispatch threads
// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
}
if (unlikely(dq->do_targetq->do_targetq)) {
return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}

_dispatch_sync_f_inline函数的ctxtfunc参数是和block相关的参数,调用的地方比较多,我们在demo工程打一个符号断点看一下,到底执行了哪个方法:

1628264358531

我们发现实际调用的是_dispatch_sync_f_slow函数

1628264502620

所以我们继续看_dispatch_sync_f_slow的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
DISPATCH_NOINLINE
static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt,
dispatch_function_t func, uintptr_t top_dc_flags,
dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
dispatch_queue_t top_dq = top_dqu._dq;
dispatch_queue_t dq = dqu._dq;
if (unlikely(!dq->do_targetq)) {
return _dispatch_sync_function_invoke(dq, ctxt, func);
}

pthread_priority_t pp = _dispatch_get_priority();
struct dispatch_sync_context_s dsc = {
.dc_flags = DC_FLAG_SYNC_WAITER | dc_flags,
.dc_func = _dispatch_async_and_wait_invoke,
.dc_ctxt = &dsc,
.dc_other = top_dq,
.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
.dc_voucher = _voucher_get(),
.dsc_func = func,
.dsc_ctxt = ctxt,
.dsc_waiter = _dispatch_tid_self(),
};

_dispatch_trace_item_push(top_dq, &dsc);
__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);

if (dsc.dsc_func == NULL) {
// dsc_func being cleared means that the block ran on another thread ie.
// case (2) as listed in _dispatch_async_and_wait_f_slow.
dispatch_queue_t stop_dq = dsc.dc_other;
return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
}

_dispatch_introspection_sync_begin(top_dq);
_dispatch_trace_item_pop(top_dq, &dsc);
_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
DISPATCH_TRACE_ARG(&dsc));
}

关注的参数依然是ctxtfunc,和上一步骤类似,我们继续打符号断点_dispatch_sync_invoke_and_complete_recurse_dispatch_sync_function_invoke来看具体执行的代码。

1628264995211

实际调用了_dispatch_sync_function_invoke函数:

1
2
3
4
5
6
static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}

继续跟进_dispatch_sync_function_invoke_inline函数:

1
2
3
4
5
6
7
8
9
10
11
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
dispatch_thread_frame_s dtf;
_dispatch_thread_frame_push(&dtf, dq);
_dispatch_client_callout(ctxt, func);
_dispatch_perfmon_workitem_inc();
_dispatch_thread_frame_pop(&dtf);
}

ctxtfunc的调用在_dispatch_client_callout函数,有多个实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DISPATCH_NOINLINE
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
_dispatch_get_tsd_base();
void *u = _dispatch_get_unwind_tsd();
if (likely(!u)) return f(ctxt);
_dispatch_set_unwind_tsd(NULL);
f(ctxt);
_dispatch_free_unwind_tsd();
_dispatch_set_unwind_tsd(u);
}
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}

虽然多处实现,但是调用block的代码都是f(ctxt)

所以block的调用链是:dispatch_sync->_dispatch_sync_f->_dispatch_sync_f_inline->_dispatch_sync_f_slow->_dispatch_sync_function_invoke->_dispatch_client_callout->f(ctxt)

同理异步函数dispatch_async用相同的方法也能探究出一个调用链,最后调用的也是f(ctxt),感兴趣的童鞋可以探究一下。