1、slowpath
与fastpath
在源码分析过程中,多次遇到过slowpath
与fastpath
的分支判断情形,最初简单以为是编译器优化选项Build Settings——Code Generation——Optimization Level
设置为Fastest,Smallest
导致的,而且以为设置为Fastest,Smallest
后就只会走fastpath
。
后来,仔细分析了slowpath
与fastpath
的源码:
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
发现就是一个比较,意思是x=1
和x=0
的几率很高,属于优化代码语句:
例如:
//lookUpImpOrForward方法
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
这个判断就是旨在behavior & LOOKUP_RESOLVER
等于0
的概率十分大,走return
几率极高。
之所以反思这个slowpath
与fastpath
,就是在断点分析lookUpImpOrForward
方法中这段代码时,明明debug
已经设置为Fastest,Smallest
,在慢查找
流程中搜索不存在的方法时,居然仍进入了if
中的return
语句,这样就反证了之前设置为Fastest,Smallest
就只走fastpath
的假设,所以导致以上反思。
所以后续代码分析不能跳过slowpath
与fastpath
的代码,必须都分析到。
2、方法签名
、Type Encodings
和``
在研究消息转发时,遇到如下方法:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"--------%@",@"methodSignatureForSelector");
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
v@:
就是方法的签名,使用消息转发多了基本上都知道字母的含义
v
:void
@
:NSString
:
:代表是方法
这是用的多的,但是其他的定义是什么还无法知道,这里就是涉及到Type Encodings,也就是代表符号的定义,可以知道这是官方的定义。反思方法签名
这个名字就更加了然,为啥叫这个名字,就是方法的一个代号,通过这个代号可以直接得到方法的结构。
与之类似的还有在研究类的结构的时候进行clang
编译main
文件时,得到的编译文件中,有一段方法列表和属性列表的编译后的代码:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[21];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
21,
{{(struct objc_selector *)"laugh", "v16@0:8", (void *)_I_Person_laugh},
{(struct objc_selector *)"cry", "v16@0:8", (void *)_I_Person_cry},
{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_run},
{(struct objc_selector *)"jump", "v16@0:8", (void *)_I_Person_jump},
{(struct objc_selector *)"doNothing", "v16@0:8", (void *)_I_Person_doNothing},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Person_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Person_setAge_},
{(struct objc_selector *)"nickname", "@16@0:8", (void *)_I_Person_nickname},
{(struct objc_selector *)"setNickname:", "v24@0:8@16", (void *)_I_Person_setNickname_},
{(struct objc_selector *)"height", "f16@0:8", (void *)_I_Person_height},
{(struct objc_selector *)"setHeight:", "v20@0:8f16", (void *)_I_Person_setHeight_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_Person_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Person_setAge_},
{(struct objc_selector *)"nickname", "@16@0:8", (void *)_I_Person_nickname},
{(struct objc_selector *)"setNickname:", "v24@0:8@16", (void *)_I_Person_setNickname_},
{(struct objc_selector *)"height", "f16@0:8", (void *)_I_Person_height},
{(struct objc_selector *)"setHeight:", "v20@0:8f16", (void *)_I_Person_setHeight_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[4];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
4,
{{"age","Ti,N,V_age"},
{"nickname","T@\"NSString\",&,N,V_nickname"},
{"height","Tf,N,V_height"},
{"name","T@\"NSString\",&,N,V_name"}}
};
其中方法的v16@0:8
之类的和Type Encodings
中的符号就完全一致的,多出来的主要是数字,这里的数字意思举个例子,在v
后面16表示整个方法占16字节,@
后面的0表示在16字节的第0字节起在内存中放置@
,在:
后面的8表示在16字节的第8字节起开始放置:
,结合Type Encodings
表,@
指针类型占8字节,:
selector类型也是8字节总计16字节,符合描述的。
顺便属性列表也推演一下,{"name","T@\"NSString\",&,N,V_name"}}
:
"T@\"NSString\"
:type
是NSString
类型,也和Type Encodings
对应上了
&
:对比age
和height
大致推测这是表示指针的地址的意思
N
:noatomic
非原子性
V_name
:对应的var
即私有变量为_name
其实这也是另一种定义Property Type String,与Type Encodings
类似,只不过是用来定义属性的一些特性,这些属性的特性可以通过方法property_getAttributes
获得。
3、高级语言类型
编译型
:使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式。(如:objective-c、java、c/c++等)
解释型
:使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译和执行,而不是在执行之前就完成翻译。(如:python、javascript、shell等)
这里的平台
的含义并不是现在网络平台这种,而是指i386
、x86_64
、arm64
这种芯片类型的平台,他有自己的一套支持机器指令集合,相同的一条机器指令,假设0000 0001
,可能在arm上是ADD
,但是在x86_64
上是MOV
。为了方便理解,ADD
和MOV
均是汇编命令。
由于iOS开发采用的是编译型
,所以这里重点介绍一下编译型
语言的流程:
编译型
的语言需要经过编译器将语言代码转化为可执行文件,这种文件在C/C++
中,可执行文件是.o
,在objective-c
中是mach-o
类型:
mach-o
文件属于二进制文件,类似windows的.exe
。
4、iOS编译流程
Objective-C
源码经过编译并不是直接形成mach-o
文件的,而是形成一个中间过程:
之前文章4、isa与对象、类的关系例举过的clang -rewrite-objc main.m -o main.cpp
命令,生成的是.cpp
文件,其中间形态肯定不是.cpp
这个阶段,和C/C++
流程类似,一样会有.i
到.s
甚至还有其他中间阶段,再到.o
,可以后续研究一下。最终会形成mach-o
,多个mach-o
有的合并,有的依赖,这个合并和依赖过程就是链接
的过程。可以类比C/C++
的运行流程:
对比
iOS
的流程:
为什么我会说多个
mach-o
的合并和依赖,这是因为我们依赖的一些系统库和第三方库都会编译生成相应的mach-o
文件,系统库因为没有暴露出来(可以在系统文件目录下找到),但是第三方库和自己生成的库可以看得到:自己新建一个framework的工程,一行代码都不用加,直接command+B,进行一个译的编,可以在工程目录Products
中的找到相应的.framework
文件,在Finder
中打开可以找到同名mach-o
文件,这个就是库的最后形态,同理.a
也可以找到。
5、动态库与静态库
结合上一节编译流程方便理解。
静态库
:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
动态库
:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。
这是对这两种库解释,静态库
的mach-o
在编译时就会复制一份到主程序的mach-o
合并了,动态库
是在运行时加载到内存,可以直接引用。
在iOS中:
静态库类型有:.a
、.framework
动态库类型有:.framework
、.dylib
、.tbd
6、强符号与弱符号
在dyld
流程分析中,_main
中有一步关键步骤弱符号绑定
:
sMainExecutable->weakBind(gLinkContext);
其中对弱符号
、强符号
存疑,查资料研究下:
在C语言中,编译器默认函数和初始化了的全局变量为强符号(Strong Symbol
),未初始化的全局变量为弱符号(Weak Symbol
)。强符号之所以强,是因为它们拥有确切的数据,变量有值,函数有函数体;弱符号之所以弱,是因为它们还未被初始化,没有确切的数据。
举个例子:
extern int ext;
int weak1;
int strong = 100;
__attribute__((weak)) weak2 = 2;
int main(){
return 0;
}
ext
不属于强弱范畴是一个外部引用;
weak1
属于弱符号;
weak2
属于弱符号;
strong
属于强符号;
main
属于强符号。
强符号
同名会在编译器报错;__attribute__((weak))
只在链接器才有效,与其他重名编译器仍然会报错;在多文件情况下,强符号
与弱符号
重名,强
会覆盖弱
,利用这点可以灵活调整定义,方便其他文件调用。