这里我们将接着上一篇文章iOS底层探索 --- 类的加载(上)继续探索.
我们来简单的回忆一下:
我们从_objc_init
->_dyld_objc_notify_register
->map_images
->_read_images
;这样一路追踪到_read_images
中。我们看到了加载镜像的10个步骤。在这10个步骤中,有对一些错乱信息的处理,类的加载,协议,分类等等信息的处理。此时我们的重点是类的加载。所以重点就在第9步。
在这里,关键的函数就是
realizeClassWithoutSwift
。所以今天我们重点来探索一下这个函数的逻辑。
在了解了我们要做什么的之前,先做一下知识点的补充,因为在realizeClassWithoutSwift
中有一些知识点,需要我们提前了解一下。
-
ro
:属于clean memory
,即;加载后不会改变内存。 -
rw
:属于dirty memory
,即;可以向类中添加属性、方法等,是在运行时可以改变的内存。 -
rwe
:相当于类的额外信息
,因为在使用过程中,只有很少的类会真正改变其内容,所以为了避免资源浪费就有了rwe
。
运行时如果需要动态向类中添加方法、协议等,会创建
rwe
,并将ro
的数据优先attach(附加)
到rwe
中。在读取时会优先返回rwe
的数据;如果rwe
没有被初始化,则返回ro
。(也即是说:① 有扩展,从rwe
中取;② 没有扩展,从ro
中取。)
rw
中包含ro
,rwe
。其目的是为了让dirty memeory
占用更少的内存,将rw
中可变的部分抽取出来作为rwe
。
clean memeory
越多越好,dirty memory
越少越好。因为iOS系统底层是虚拟内存机制,在内存不足的情况下,会将一部分内存回收;之后使用时再从磁盘中加载。
clean memeory
是可以从磁盘中重新加载的内存,例如动态库,Mach-O
文件等。
dirty memory
是运行时产生的数据,不能从磁盘中重新加载;所以必须一直占用内存。当系统物理内存紧张时,会回收
clean memory
;如果dirty memeory
过大,则会直接回收掉。设计
ro
、rw
、rwe
的目的是为了更好更细致的区分clean memory
和dirty memeory
。
realizeClassWithoutSwift
同样的,我们首先来看一下源码是怎么写的:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 对类 cls 执行首次初始化
* including allocating its read-write data. 包括分配其读写数据
* Does not perform any Swift-side initialization. 不执行任何 Swift 端初始化
* Returns the real class structure for the class. 返回类的真实类结构
* Locking: runtimeLock must be write-locked by the caller 锁定:runtimeLock 必须由调用者写锁定
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
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();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
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
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
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);
// 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);
// 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;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
大家注意看官方对该函数的注释,这里给出了翻译。realizeClassWithoutSwift
方法的主要所用就是实现类,官方注释写的很明白Returns the real class structure for the class
,返回一个真实的类结构。
主要有以下几个步骤:
- 1、读取
data
数据,设置ro
,rw
。 - 2、递归
realizeClassWithoutSwift
(这里回忆一下isa走位图
)。 - 3、设置类的一些相关信息,子类,父类等等。
- 4、
methodizeClass
,附加类别。
1、读取data
数据,设置ro
,rw
在上面我们已经介绍过ro
和rw
的相关定义。
2、递归realizeClassWithoutSwift
这里会递归调用realizeClassWithoutSwift
,对于当前类的父类,元类进行初始化。
3、设置类的一些相关信息,子类,父类等等
在得到了父类和元类之后,对当前类进行配置。当然,上面有递归调用,所以,或有if
判断,看一下当前类的类型。
4、methodizeClass
,附加类别
我们跟踪进methodizeClass
之后,首先还是要阅读官方注释;通过过官方注释我们得知,这个方法是修正cls
的方法列表、协议列表、属性列表。
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
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);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name()));
}
ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
}
#endif
}
- 这里虽然官方注释是
Attach categories
,但是大家不要认为只是附加分类中的方法。因为在源码中有这样一段注释,大家仔细看一下:
看一看到这里对于方法,属性的处理,是包含类本身的方法,属性等信息的。对于协议也是一样的,因为源码中也获取了baseProtocols
:
最终,方法列表
、属性列表
、协议列表
,都附加到了rwe
中。
4.1 prepareMethodLists
上面我们看到,对于方法列表
的处理,并不是直接添加到调用rwe->methods.attachLists(&list, 1);
。在附加之前,还调用了prepareMethodLists
函数;那么这个函数又是做什么的呢?我们跟进去看一下,最终发现了这样一段代码。(注意:这里list
传入的是&list
)
也就是说,方法列表
在附加到rwe
中之前,做了一次排序。(通过sore
可以推测,注意上图中红圈。)
4.2 fixupMethodList
上面我们追踪到了fixupMethodList
,推测到这是一个对方法列表
进行排序的函数。那究竟是怎么排序的呢?我们进入函数看一下:
可以看到,
方法列表
是根据selecor address
来进行排序的。
同时,在排序之前,根据if
的判断,还会进行一次name(返回的SEL)
的设置。(看图中,红框上方的代码块。)
- 这个地方我们可以通过打印验证一下。我们在
fixupMethodList
中,打印一下,排序前后的methodlist
:
- 我们自定一个类,并调用一下,(注意:这里要调用一下
load
方法,不然系统会默认为懒加载) -
接下来,运行工程(源码工程),在控制台的打印里面搜索我们自定义的方法(因为有系统的一些打印,会比较多。),可以看到方法按照地址排了序:
补充:在方法的
慢速查找
,会遇到一个二分查找的方法。二分查找的基础就是方法列表
已经被排序。这里我们用
伪代码
简单实现以下二分查找
: