之前的一篇文章简略的介绍了objc runtime的在程序启动之前都做了哪些事情,接下来我们深入到细节中看看,应用链接库中的类及应用本身定义的类被映射进内存并建立关联的过程以及objc是通过什么结构去管理类之间的这种复杂联系的。
内存映射
编译链接过程:
编译器根据代码生成可执行文件(Windows平台下为PE文件格式、Linux平台下为ELF文件格式、Mac平台下为Mach-O文件格式)的过程,每一种可执行文件格式都有其特定的文件结构,Mach-O文件格式请参阅这篇文章,其他平台的可执行文件格式与Mach-O类似,特定segment及其section都有着特定的含义,但是无论哪个平台的可执行文件格式其文件中肯定会包含data
,text
,stack
等segment。Mach-O与其他平台可执行文件格式不同之处在于:
OBJC2.0之前:
编译器专门为Objective-C类及方法在Mach-O可执行文件中开辟了一个叫做__OBJC
的segment,其中包含了一切与Objective-C语言相关的类信息,包括类库和应用重定义的类。
OBJC2.0之后:
编译器将这些跟Objective-C语言相关的信息存储到了__TEXT
和__DATA
segment中,名称为于__objc_classname
,__objc_methname
,__objc_classlist
,__objc_imageinfo
等的section中,其中__objc_imageinfo
section中记录着应用(应用的可执行文件并不是xxx.app文件,而是xxx.app文件目录下的xxx文件)执行所需要链接的库信息(lib/framework),__objc_classlist
section中记录着本应用中声明的类信息。
程序执行过程
对于Mac/iOS系统执行程序时,首先从Mach-O文件中读取framework和应用中定义的类信息,然后对这些类建立connection,最终会存储在一个全局的(extern)NSMapTable
结构中,名称为class_hash
,等待代码运行时去使用已经建立connection的类。
在OBJC2.0之前:
详见objc-file-old.mm文件中宏定义:
1 | #define GETSECT(name, type, sectname) \ |
表明类的信息是从SEG_OBJC
即为__OBJC segment中读取得到的。
而在OBJC2.0之后:
详见objc-file.mm文件中的宏定义
1 | #define GETSECT(name, type, sectname) \ |
表明类的信息是从SEG_DATA
即为__DATA
segment中读取得到的。
细心地朋友肯能会发现在objc-runtime-old.mm文件中有这样一段注释,他的大概意思是:
- 当images被加载完成(在程序启动或其他情况下),runtime需要从中加载classes和categories,将classes与superclasses建立关联,categories与parent classes建立关联,然后调用+load方法。
- runtime可以以任意顺序对classes进行处理,也就是说,runtime可能先于superclasses发现class,为了处理这种无序的class加载顺序,runtime建立了一种”pending class”机制。
- 一个class在image中第一次被发现时被视为”unconnected”,它被存储在
unconected_class_hash
中。如果该class的所有superclass都存在并且已经被”connected”,这个新的class可以被connected进他的superclasses并被移到class_hash
中以便被使用。否则该class会一直被存储在unconnected_class_hash
中知道superclasses完成connecting。 - image mapping并”不是当前线程安全”的操作,它会在一些情况下被重新执行那个:superclass找到了一些引起ZeroLink的原因时回去加载另一个image,或者调用+load的方法时dyld会去加载另一个image。
image mapping 顺序为:
- 读取images中所有的classes
- 读取images中所有的categories
- 对所有classes建立connection
- 处理selector和class的引用计数
- 修复images中所有protocol对象
- 调用
+load
方法
详见:
1 | * Read all classes in all new images. |
建立Classes之间的连接
从images中读取classes后需要构建classes之间的关系网,而构建这一关系网的基础结构是NXMapTable
和NXHashTable
,熟悉这两个结构及其操作函数有助于我们对构建classes之间connection关系部分代码的理解。根据名称我们可以明显的看出这两个结构是基于hash表结构构建的。NXMapTable
与NXHashTable
结构及其操作函数详见项目代码Gitub。
NXHashTable
结构体如下:
1 | /** |
重点的宏定义的理解:
1 | /* BUCKETOF是一个宏,它返回指定data值经过hash后的数组地址 |
NXHashTable数据操作的内存布局如下图:

注意:
在NXHashTable结构中
prototype
的hash函数决定了index的获取规则分为通过指针获取还是字符串获取,NXHashTable中的hash函数的参数一般式指针即(Ptr)count
代表存储的HashBucket的数量nbBucket
代表bucket容量的大小一般为2^n-1bucket
指向存储HashBucket的数组的指针
在HashBucket结构中
count
代表该HahsBucket结构存储元素的数量- 当存储一个元素时直接存储到
void *one
中,当存储多于一个元素时存储到void **many
代表的二维数据中
NXMapTable
NXMapTable与NXHashTable不同之处在于,NXMapTable的buckets指向存储MapPair结构的数组,即:
1 | typedef struct _MapPair { |
NXMapTable数据操作的内存布局如下图:

注意:
在NXMapTable中
pototype
的hash函数决定了index的获取规则分为通过指针获取还是字符串获取,NXMapTable中的hash函数的参数一般式字符串(Str)count
代表存储的MapPair的数量nbBucketMinusOne
代表bucket容量的大小一般为2^n-1buckets
指向存储MapPair的数组的指针
在MapPair中
key
一般代表指向str的指针,所以该结构一般用于存储有名称的对象,如selctor,class,protocol等value
一般存储与key存储的字符串相关联的结构体.
metaclass的理解
1 | /** |