前言
前文iOS底层原理之OC类的加载原理(上)分析了_objc_init
->map_images
->_read_images
的整个流程,最后定位到类的加载是在realizeClassWithoutSwift
函数中完成的。本文将接着上文继续分析。
准备工作
- dyld源码。
- objc4-818.2源码。
一: realizeClassWithoutSwift
函数
实现了+load
方法的类在_read_images
函数中会进入此函数,此函数对类进行了加载,并递归加载父类、元类,包括分配读写数据,返回类的真实类结构。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
class_rw_t *rw;
Class supercls;
Class metacls;
...
// 1. 生成rw数据
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
// 2. cache初始化
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
// 元类处理
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
// 为32位设计的,objc_indexed_classes_count 为index
// 存入objc_indexed_classes中(其中记录了index-cls的关系)
// 为isa是否纯指针做的处理
cls->chooseClassArrayIndex();
...
// 3. 关联父类与元类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// 4. 调整ivars
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// 5. 同步flags标志位
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// 6. 串联继承链
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// 7. Attach categories
// Attach categories
methodizeClass(cls, previously);
return cls;
}
- 通过
ro
生成rw
数据。 cache
初始化。- 关联父类与元类。递归加载父类和元类,然后与当前类关联起来。
- 调整
ivars
的offsets/layout
。 - 同步
flags
的标志位给rw
。 - 串联类的继承链。
- 分类附加到主类的逻辑在
methodizeClass
函数中,后文单独分析。
1.1: 生成rw
数据
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
// 调试代码 开始
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "HPObject") == 0 && !isMeta) {
printf("%s %s\n",__func__,mangledName);
}
// 调试代码 结束
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
// 发生混乱的类(内存移动,但未删除),非懒加载实现
// rw 数据已经实现过了
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
// 普通类,rw数据未分配,开辟rw空间
rw = objc::zalloc<class_rw_t>();
// 将ro数据存入rw中。这也是为什么从data()->ro()的原因。
rw->set_ro(ro);
//flags标志位设置 uint32_t 1<<31 1<<19 1<<0 (元类为1,非元类为0)
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
//设置rw数据,这个时候data()拿出来的就是rw数据了。
cls->setData(rw);
}
- 首先通过
cls->data()
获取ro
数据,此前类的原理探索篇章data()
获取的是rw
数据,这是因为此时rw
还未赋值,只是从macho
文件中名为__objc_classlist
或__objc_nlclslist
的section
中对应类的data
中读取数据存入了data()
中。具体可以在赋值前后验证cls->data()
的地址。 - 混乱的类:
- 混乱的类就重新设置下
rw
和ro
,并调用changeInfo
函数修改下rw
的flags
;
- 混乱的类就重新设置下
- 普通类:
- 普通类就开辟
rw
空间,将ro
存入到rw
中(只是将地址给了rw
,并不会完全复制一份,苹果对这块做了相应的优化)。 - 设置
rw
的flags
。flags
是一个uint32_t
类型,第31
位表示是否实例化完毕(RW_REALIZED
),第19
位表示是否实例化中(RW_REALIZING
),第0
位表示是否是元类(元类为1
,非元类为0
)。 - 将
rw
存入data
中。
- 普通类就开辟
⚠️因为元类和类的名称相同,元类和类都会进入
realizeClassWithoutSwift
函数加载,所以在调试代码部分加了判断isMeta
,防止元类影响我们调试。
ro
数据在llvm
编译阶段就已经生成了。
class_ro_t
在llvm
中的定义。
class_ro_t::read
函数实现:
- 根据
class_ro_t
结构体的成员变量计算出size
。- 根据传入的
addr
获取size
大小的buffer
。- 通过
buffer
提取address
里的数据。- 对
class_ro_t
结构体成员变量进行赋值。
class_ro_t::Read
函数是被Read_class_row
调用的。
lldb
调试验证ro
数据在类加载进内存时就有了:
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080a8
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 128
instanceStart = 8
instanceSize = 8
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "HPObject" {
Value = 0x0000000100003f2e "HPObject"
}
}
baseMethodList = 0x00000001000080f0
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x00000001000080f0
(lldb) p *$2
(lldb) p $1.baseMethods()
(method_list_t *) $3 = 0x00000001000080f0
(lldb) p *$3
(method_list_t) $4 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $4.get(0).big()
(method_t::big) $5 = {
name = "instanceMethod"
types = 0x0000000100003f57 "v16@0:8"
imp = 0x0000000100003ea0 (HPObjcTest`-[HPObject instanceMethod])
}
- 获取
ro
后,输出baseMethodList
没有显示任何数据,改为调用baseMethods()
函数获取。 - 然后调用
get(0).big()
获取方法的详细信息。
1.1.1: cls->data()
获取ro
数据探索
我们先来看看data()
函数的定义:
struct objc_class
中的data()
函数定义。
class_rw_t *data() const {
return bits.data();
}
- 实际调用的是
struct class_data_bits_t
中的data()
函数。
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
bits & FAST_DATA_MASK
,然后强转成class_rw_t *
返回。
bits
为uintptr_t(unsigned long)
,也就是指针地址的10
进制表现形式,FAST_DATA_MASK
为0x00007ffffffffff8UL
,bits & FAST_DATA_MASK
结果也就是一个指针,然后强转成class_rw_t
指针返回。
然后返回到realizeClassWithoutSwift
函数后,又强转成了class_ro_t
指针(class_rw_t
包含class_ro_t
,所以可以强转)。
那么既然返回的是指针,又是怎么变成class_ro_t
结构体的呢?
class_ro_t
结构体指针就指向这个结构体的首地址,读值、写值就是通过地址偏移的方式对结构体成员变量挨个读取、写入。如果指针类型与数据结构类型不匹配,就会解析失败。
1.1.2: 指针还原数据结构验证
下面通过Method
(方法)指针还原objc_method
结构体的方式来验证:
众所周知,我们可以通过runtime
的相关API
来获取方法,但是实际上返回的也是方法指针。
Method *me = class_getInstanceMethod([XJPerson class], @selector(loveEveryone));
Method
在底层有两种定义:
// 非源码环境也可以看到
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
};
// 源码环境才可以看到
typedef struct method_t *Method;
// 省略方法和静态成员变量
struct method_t {
...
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
...
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
};
...
}
- 虽然
objc_method
结构体的成员变量注明OBJC2_UNAVAILABLE
,但是却是苹果在非源码提供给我们的数据类型,也可以用来测试。 method_t
结构体由于比较复杂,我们可以直接简化来进行测试。
- 可以看到都顺利的由指针解析出了对应的结构体数据。
所以ro
数据是从macho
文件读取对应的地址指针后针对class_ro_t
结构体解析得到。
method_t
在llvm
中的定义:
method_t::read
函数实现:
- 计算出
size
。- 根据传入的
addr
获取size
大小的buffer
。- 通过
buffer
提取address
里的数据。- 对
method_t
结构体成员变量进行赋值。
method_t::Read
函数是被Describe
调用的。
1.2: cache
初始化
cache
初始化为空缓存。
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
void cache_t::initializeToEmpty()
{
_bucketsAndMaybeMask.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed);
_originalPreoptCache.store(nullptr, std::memory_order_relaxed);
}
struct objc_cache _objc_empty_cache =
{
0, // mask
0, // occupied
{ NULL } // buckets
};
- 最终调用到
cache_t::initializeToEmpty
函数将cache
初始化为空缓存。
1.3: 关联父类与元类
// 父类和元类的加载
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// 元类isa是纯指针
cls->setInstancesRequireRawIsa();
} else {
// isa是否纯指针, flags中第13位
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
// 这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISA
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
// 配置环境变量为YES后,isa是一个纯指针。
instancesRequireRawIsa = true;
}
// OS_object类时纯指针
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
// 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。
// rawIsaIsInherited 表示继承的是纯指针
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
// 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)
// rawIsaIsInherited只是控制打印
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// 关联父类与元类。也就是继承链与isa指向链
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
- 递归加载父类和元类。
- 判断设置
isa
是否为纯指针。- 元类
isa
是纯指针。 - 类的
isa
是否为纯指针取决于flags
的第13
位。 DisableNonpointerIsa
也就是OBJC_DISABLE_NONPOINTER_ISA
环境变量配置来指定isa
是否为纯指针。OS_object
的isa
为纯指针。- 父类
isa
是纯指针,并且父类还有父类。那么本类也要是纯指针。rawIsaIsInherited
(只用于控制打印语句)表示纯指针是继承的。 - 递归设置子类
isa
为纯指针(父类isa
为纯指针,子类isa
也要为纯指针)。
- 元类
- 关联父类与元类。也就是继承链与
isa
指向链。
1.4: 调整ivars
的offsets/layout
// 调整ivar的offset,可能会重新创建`class_ro_t`来更新ivar
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// 设置成员变量占用空间大小
cls->setInstanceSize(ro->instanceSize);
- 在有父类,并且非元类的情况下,调用
reconcileInstanceVariables
函数协调成员变量偏移/布局。 - 调用
setInstanceSize
函数重新设置instanceSize
(实例变量)大小,并且如果尚未设置cache
的fastInstanceSize
,就设置它。
成员变量的解读将在下篇文章分析,本文重点是方法的加载。
1.5: ro
同步flags
标志位给rw
// 拷贝ro的flags到rw中
// flags第2位 c++构造/析构函数 RO_HAS_CXX_STRUCTORS,
// flags第8位 只有c++析构函数 RO_HAS_CXX_DTOR_ONLY
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
// 至少有析构函数
cls->setHasCxxDtor();
// 不只有析构函数
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
// 构造函数也有
cls->setHasCxxCtor();
}
}
// 是否禁止关联对象 第20位标记禁止关联对象。
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
- 拷贝
ro
的flags
到rw
中(C++
构造和析构函数flag
是放在cache
中)。RO_HAS_CXX_STRUCTORS
,flags
第2
位,C++
构造/析构函数。RO_HAS_CXX_DTOR_ONLY
,flags
第8
位,只有C++
析构函数。
- 本类禁止关联对象或者父类禁止关联对象(父类禁止,子类也需禁止),则设置
RW_FORBIDS_ASSOCIATED_OBJECTS
标记。RW_FORBIDS_ASSOCIATED_OBJECTS
,flags
第20
位,禁止关联对象。
1.6: 串联类的继承链
if (supercls) {
//关联子类
addSubclass(supercls, cls);
} else {
//设置根类 nextSiblingClass 为 _firstRealizedClass 根类是第一个被实例化的类。
addRootClass(cls);
}
- 本类有父类的情况下,将本类连接到其父类的子类列表。
- 设置本类的相邻类。
- 本类继承父类
C++
构造/析构函数的flag
。 - 本类继承父类
RW_NOPREOPT_CACHE
和RW_NOPREOPT_SELS
的flag
。 - 本类继承父类
FAST_CACHE_REQUIRES_RAW_ISA
/RW_REQUIRES_RAW_ISA
的flag
。
- 本类无父类的情况下,将本类设置为新的根类。
1.6.1: addSubclass
static void addSubclass(Class supercls, Class subcls)
{
...
if (supercls && subcls) {
...
objc_debug_realized_class_generation_count++;
// 相邻类
subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
// 第一个子类
supercls->data()->firstSubclass = subcls;
// 类继承父类`C++`构造/析构函数的`flag`
if (supercls->hasCxxCtor()) {
subcls->setHasCxxCtor();
}
if (supercls->hasCxxDtor()) {
subcls->setHasCxxDtor();
}
...
// 本类继承父类`RW_NOPREOPT_CACHE`和`RW_NOPREOPT_SELS`的`flag`
if (!supercls->allowsPreoptCaches()) {
subcls->setDisallowPreoptCachesRecursively(__func__);
} else if (!supercls->allowsPreoptInlinedSels()) {
subcls->setDisallowPreoptInlinedSelsRecursively(__func__);
}
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
// 本类继承父类`FAST_CACHE_REQUIRES_RAW_ISA`/`RW_REQUIRES_RAW_ISA`的`flag`
// 根元类不继承
if (supercls->instancesRequireRawIsa() && supercls->getSuperclass()) {
subcls->setInstancesRequireRawIsaRecursively(true);
}
}
}
- 设置本类的相邻类为父类的第一个子类(首次为
nil
)。 - 设置本类为父类的第一个子类。
- 本类继承父类
C++
构造/析构函数的flag
。 - 本类继承父类
RW_NOPREOPT_CACHE
和RW_NOPREOPT_SELS
的flag
(共享缓存和inlined sels
)。 - 本类继承父类
FAST_CACHE_REQUIRES_RAW_ISA
/RW_REQUIRES_RAW_ISA
的flag
(isa
是否为纯指针)。根元类不继承。
1.6.2: addRootClass
static void addRootClass(Class cls)
{
...
objc_debug_realized_class_generation_count++;
// 本类的相邻类设置为第一个加载的类(nil)
cls->data()->nextSiblingClass = _firstRealizedClass;
// 第一个加载的类设置为自己
_firstRealizedClass = cls;
}
- 本类无父类的情况下,将本类的相邻类设置为nil,并将本类设置为第一个加载的类,即根类。
二: methodizeClass
realizeClassWithoutSwift
函数最后调用了methodizeClass
函数来对主类方法列表进行排序和加载分类。
// Attach categories
methodizeClass(cls, previously);
-
previously
参数为从_read_images
中传过来的nil
。realizeClassWithoutSwift(cls, nil);
methodizeClass
函数核心逻辑如下:
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
// rwe这里通常获取的为NULL,因为此时还没有调用extAllocIfNeeded函数创建rwe
auto rwe = rw->ext();
...
// Install methods and properties that the class implements itself.
// 安装类自己实现的方法和属性和协议
// 方法、属性、协议列表通常不是在此处添加进rwe的,
// 因为创建rwe时就添加了
// 获取ro的方法列表
method_list_t *list = ro->baseMethods();
if (list) {
// isBundleClass,判断类是否在一个不可加载的捆绑包中,绝不能由编译器设置
// RO_FROM_BUNDLE,第29位
// 方法列表排序(按sel地址)
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
// 如果有rwe,就将主类的方法列表添加到rwe的methods
if (rwe) rwe->methods.attachLists(&list, 1);
}
// 获取ro的属性列表
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
// 有rwe且proplist不为NULL
// 就将主类的属性列表添加到rwe的properties
rwe->properties.attachLists(&proplist, 1);
}
// 获取ro的协议列表
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
// 有rwe且协议列表不为NULL
// 就将主类的协议列表添加到rwe的protocols
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
// 如果是根元类,就添加initialize方法
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
// 分类附加到主类,previously == nil,走下面流程
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS);
}
}
// 没有分类的情况下不会走
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
...
}
rwe
这里通常获取的为NULL
,因为此时还没有调用extAllocIfNeeded
函数创建rwe
。- 主类方法、属性、协议处理。
- 主类的方法列表修正和排序(按
sel
地址排序)。 - 有
rwe
才会执行下面流程,通常不会执行。- 主类的方法列表添加到
rwe
的methods
。 - 主类的属性列表添加到
rwe
的properties
。 - 主类的协议列表添加到
rwe
的protocols
。
- 主类的方法列表添加到
- 主类的方法列表修正和排序(按
- 如果是根元类,就添加
initialize
方法。 - 分类的
attachToClass
处理。(此类没有分类的情况下不会执行)。
2.1: prepareMethodLists
// 主类方法排序传参
// cls, &list, 1, YES, isBundleClass(cls), nullptr
// 分类方法排序传参,分类mlists里面存的是method_list_t *类型
// cls, mlists, mcount, NO, fromBundle, __func__
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
……
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
// addedCount 主类方法排序时为 1,分类方法排序时需要看加载的数量
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// Fixup selectors if necessary
// 如果没有排序,就修正且排序
if (!mlist->isFixedUp()) {
// 修正并且排序
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
……
}
isBundleClass
,判断类是否在一个不可加载的捆绑包中,绝不能由编译器设置。RO_FROM_BUNDLE
,第29位。addedLists
为**
类型。那么mlist
就是主类方法列表的指针或某一个分类方法列表的指针。- 如果没有排序,就修正且排序
ro->baseMethods()
。
2.1.1: fixupMethodList
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
// SEL转成name字符串
const char *name = sel_cname(meth.name());
// 将name和地址设置进meth中
printf("before setName:%s address:%p\n",name,meth.name());
// 设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,
// 相当于以dyld的为准。
meth.setName(sel_registerNameNoLock(name, bundleCopy));
printf("after setName:%s address:%p\n",name,meth.name());
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
// 排序,通过SEL的地址排序。samll lists 不可变,不排序。
// 这里就与慢速消息查找的二分查找对应上了。在这里进行的排序。
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
// 将方法列表标记为已修正且排序
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
- 先对
method
的SEL
进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector
。相当于以dyld
的为准。 - 接着以
SEL
的地址对methodList
进行排序,这里就与慢速消息查找流程的二分查找对应上了。排序是在这里进行的。 - 最后将方法列表标记为已修正且排序。
small lists
情况下不进行排序,因为它们是不可变的。
SEL
修正前后:
排序验证:
三: 分类探索
methodizeClass
函数里面有针对rwe
方法列表、属性列表和协议列表等等的相关处理,由于没有rwe
数据,所以相关流程都不会执行。根据之前WWDC
的介绍知道在有分类或者动态给类添加方法、属性、协议的情况下会有rwe
数据,所以添加个分类来进行探索。
@interface XJPerson (CatA)
@property (nonatomic, copy) NSString *cata_Name;
@property (nonatomic, assign) int cata_age;
- (void)cata_instanceMethod1;
- (void)cata_instanceMethod2;
+ (void)cata_classMethod1;
@end
@implementation XJPerson (CatA)
- (void)cata_instanceMethod1
{
NSLog(@"%s", __func__);
}
- (void)cata_instanceMethod2
{
NSLog(@"%s", __func__);
}
+ (void)cata_classMethod1
{
NSLog(@"%s", __func__);
}
@end
3.1: clang
还原底层代码探索
为了方便研究分类的实现原理,先通过clang
探索下底层的实现。
通过clang
指令将.m
文件文件编译成.cpp
。
clang -rewrite-objc main.m -o main.cpp
在.cpp
文件里搜索分类的名字CatA
,在文件的最后面找到了如下的定义:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_XJPerson_$_CatA,
};
_OBJC_$_CATEGORY_XJPerson_$_CatA
是_category_t
的结构体类型。
接着搜索_category_t
:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
- 分类在底层也是结构体类型。
- 成员变量
name
应该是分类的名字CatA
。 cls
指向类。- 类只有一个
methods
(方法列表),但是分类却有instance_methods
和class_methods
,这是因为分类没有元类(即没有分元类)。 - 分类也有
protocols
和properties
。
_OBJC_$_CATEGORY_XJPerson_$_CatA
定义:
static struct _category_t _OBJC_$_CATEGORY_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"XJPerson",
0, // &OBJC_CLASS_$_XJPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_XJPerson_$_CatA,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_XJPerson_$_CatA,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_XJPerson_$_CatA,
};
- 这里
name
是XJPerson
而不是CatA
,是因为静态编译的时候还不知道分类的名字,所以赋值了类的名字。 cls
没有赋值,但是有注释,是因为此时还未关联,需要运行时进行关联。- 因为分类没有遵守协议,所以协议为空。
遵守NSObject
协议后,重新编译查看:
- 此时协议就有值了,正是
NSObject
协议。
继续查看属性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"cata_Name","T@\"NSString\",C,N"},
{"cata_age","Ti,N"}}
};
- 属性列表能找到分类声明的相应的属性。
继续查看方法列表:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"cata_instanceMethod1", "v16@0:8", (void *)_I_XJPerson_CatA_cata_instanceMethod1},
{(struct objc_selector *)"cata_instanceMethod2", "v16@0:8", (void *)_I_XJPerson_CatA_cata_instanceMethod2}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"cata_classMethod1", "v16@0:8", (void *)_C_XJPerson_CatA_cata_classMethod1}}
};
- 并没有自动生成属性对应的
setter & getter
方法。所以分类属性的setter & getter
方法只能通过关联对象处理。
3.2: category_t
源码验证
objc
源码搜索category_t
查看相关定义:
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
// 此变量并不总是出现在磁盘上
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
objc
源码里的category_t
结构与clang
转换的.cpp
文件里的_category_t
结构基本相同,只是category_t
多了一个_classProperties
。根据注释此变量并不总是出现在磁盘上,所以暂且不研究它。
3.3:官方文档探索
Xcode
里同时按住command
、shift
和0
打开官方文档,搜索Category
,查看相关说明:
- 文档里
Category
是objc_category
的重命名。
objc
源码里搜索objc_category
结构体探索:
objc_category
在objc 2.0
已经不可用了。可见苹果文档分类相关内容多久没更新了,也有可能是苹果为了隐藏底层真实的实现故意为之。
3.4: 源码探索分类加载
经过上面的探索,大致了解了分类的本质:分类和类一样,本质也是一个结构体。那么下面就探索下分类是怎么加载的。
methodizeClass
函数里分类附加到本类的核心函数是attachLists
和attachToClass
,如果rwe
存在,就会调用attachLists
函数将本类方法、属性、协议添加到rwe
中,但是通常此时rwe
并未创建。所以我们先来梳理下分类的加载流程。
3.4.1: rwe
创建时机分析
methodizeClass
函数里rwe
获取方式:
auto rwe = rw->ext();
ext
函数实现:
struct class_rw_t {
...
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
// 获取rwe
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
// 存在就返回地址指针
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
// 不存在就创建rwe,设置rwe,并返回地址指针
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
// rwe深拷贝
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
// 开辟内存
auto rwe = objc::zalloc<class_rw_ext_t>();
// 元类version为7,非元类为0
rwe->version = (ro->flags & RO_META) ? 7 : 0;
// 主类的方法,属性,协议是在此处添加进rwe中的
// 即创建完rwe就添加
// methodizeClass函数不会添加,因为那时通常还没有rwe
// 获取主类方法列表
method_list_t *list = ro->baseMethods();
if (list) {
// deepCopy默认为false,如果为true就复制一份
// class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
// extAlloc函数的声明可以说明
if (deepCopy) list = list->duplicate();
// 将主类方法列表添加到rwe的methods中
// methodizeClass函数已经进行过修复并排序了
rwe->methods.attachLists(&list, 1);
}
// See comments in objc_duplicateClass
// property lists and protocol lists historically
// have not been deep-copied
//
// This is probably wrong and ought to be fixed some day
// 获取主类属性列表
property_list_t *proplist = ro->baseProperties;
if (proplist) {
// 将主类属性列表添加到rwe的properties中
rwe->properties.attachLists(&proplist, 1);
}
// 获取主类的协议列表
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
// 将主类协议列表添加到rwe的protocols中
rwe->protocols.attachLists(&protolist, 1);
}
// 设置rwe和ro
set_ro_or_rwe(rwe, ro);
// 返回rwe
return rwe;
}
...
}
ext
函数获取rwe
。extAllocIfNeeded
函数判断rwe
是否存在,存在就返回,不存在就调用extAlloc
函数进行rwe
的创建。extAlloc
函数进行rwe
的创建,并将主类的方法列表,属性列表,协议列表添加进rwe
对应的列表中(不是在methodizeClass
函数添加的,因为那时还没有rwe),然后设置rwe
和ro
,并返回rwe
。
综上所述,只有调用了extAllocIfNeeded
函数才会创建rwe
,全局搜索发现调用extAllocIfNeeded
函数的有以下函数(rwe
创建情况):
attachCategories
函数。objc_class::demangledName
函数(isRealized() || isFuture()
的情况下)。class_setVersion
函数。addMethods_finish
函数。class_addProtocol
函数。_class_addProperty
函数。objc_duplicateClass
函数。
可以看到除了attachCategories
,其它要么是对类进行动态处理,要么是修复类的时候创建rwe
。这与WWDC
上的介绍就相吻合了。我们现在探索的是分类的加载流程,那么显然核心逻辑就在attachCategories
函数了。
3.4.2: 分类加载流程初探
全局搜索发现attachCategories
函数是在attachToClass
函数和load_categories_nolock
函数中调用的。
attachToClass
函数调用了attachCategories
函数:
load_categories_nolock
函数调用了attachCategories
函数:
3.4.2.1: 分类加载流程一
全局搜索attachToClass
,发现只在methodizeClass
函数中调用了。
// 二大节分析过了,此处只贴出加载分类相关代码
static void methodizeClass(Class cls, Class previously)
{
...
// Attach categories.
// previously传过来的是nil,不走if里面流程
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
// 走此处流程
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
...
}
methodizeClass
函数前面分析过是被realizeClassWithoutSwift
函数调用的。previously
这个判断条件在源码里都为nil
,可能是备用参数,作为苹果内部调试使用的。
结合前面分析可以得出分类加载流程一:
map_images
-->map_images_nolock
-->_read_images
-->realizeClassWithoutSwift
-->methodizeClass
-->attachToClass
-->attachCategories
-->attachLists
。
3.4.2.2: 分类加载流程二
全局搜索load_categories_nolock
函数,发现是被loadAllCategories
函数和_read_images
函数调用的。前面分析过了分类的加载必须在load_images
函数调用之后,所以就只需要接着探索loadAllCategories
函数了。
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
继续搜索loadAllCategories
函数,发现是被load_images
函数调用的。
static bool didInitialAttachCategories = false;
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
// didInitialAttachCategories为true时才会走`_read_images`里面的`load_categories_nolock`,
// 但是下一行代码就调用了loadAllCategories
// 此处设置为true,以保证loadAllCategories只调用一次
didInitialAttachCategories = true;
// 加载分类
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
didInitialAttachCategories
默认是false
,执行loadAllCategories
函数前将didInitialAttachCategories
设为true
,以保证只调用一次loadAllCategories
函数。- 在
_objc_init
函数调用_dyld_objc_notify_register(&map_images, load_images, unmap_image)
注册完回调后didCallDyldNotifyRegister = true
。
经过分析可以得出分类加载流程二:
load_images
-->loadAllCategories
-->load_categories_nolock
-->attachCategories
-->attachLists
。
分类加载的详细流程将在下文继续分析。
总结
realizeClassWithoutSwift
核心逻辑以思维导图的形式展示如下: