OC底层 - 类的加载(2)
前言
一、realizeClassWithoutSwift
二、methodizeClass
三、懒加载类和非懒加载类
四、分类的本质
五、classProperties
通过对objc_init的分析以及map_images->_read_images->readClass这几个核心函数的初识之后,我们可以清晰的知道从dyld的启动加载->objc_init->_dyld_objc_notify_register,到把类从macho里面加载到内存的过程。但是类的属性、方法、协议等是怎么读取到rw和ro里面呢?带着还未解决的问题,我们继续来分析类的加载。
一、realizeClassWithoutSwift
回顾read_images中的核心方法 , 我们主要研究自定义的MyClassTest类:
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count); //对应macho __objc_nlclslist
for (i = 0; i < count; i++) {
//classref_t 在启动时未修复; 使用 remapClass() 进行转换
Class cls = remapClass(classlist[i]);
const char *mangleName = cls->mangledName();
const char *MyClassTest = "MyClassTest";
if (strcmp(mangleName, MyClassTest) == 0) {
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (isMeta) {
printf("classLoad Meta - nlclslist->_getObjc2NonlazyClassList: %s\n",MyClassTest);
} else {
printf("classLoad noMeta - nlclslist->_getObjc2NonlazyClassList: %s\n",MyClassTest);
}
}
if (!cls) continue;
//将一个类添加到所有类的表中。这个表就是runtime_init的时候创建的
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
const char *mangleName = cls->mangledName();
const char *MyClassTest = "MyClassTest";
if (strcmp(mangleName, MyClassTest) == 0) {
printf("classLoad - resolvedFutureClasses: %s\n",MyClassTest);
}
}
free(resolvedFutureClasses);
}
if (DebugNonFragileIvars) {
realizeAllClasses();
}
在read_images函数中,以上有3处都有实现class的函数: realizeClassWithoutSwift 和 realizeAllClasses。
在探索方法的慢速查找流程:lookUpImpOrForward ,当类没有初始化时,最终也会调用realizeClassWithoutSwift。
所以,我们先来具体的逐段分析一下realizeClassWithoutSwift函数。
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
const char *mangleName = cls->mangledName();
const char *MyClassTest = "MyClassTest";
if (strcmp(mangleName, MyClassTest) == 0) {
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (isMeta) {
printf("classLoad Meta - realizeClassWithoutSwift: %s\n",MyClassTest);
} else {
printf("classLoad noMeta - realizeClassWithoutSwift: %s\n",MyClassTest);
}
}
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?
//从macho中读取data,按照class_ro_t的格式强制转换成ro
auto ro = (const class_ro_t *)cls->data();
可以看到核心的地方:auto ro = (const class_ro_t *)cls->data();
此时就是从macho中读取data,按照class_ro_t的格式强制转换成ro,那么我们来打印一下ro中的内容:

通过lldb,可以打印出我写在 MyClassTest 中的 testInstance的方法,那么属性,成员变量以及协议都可以打印出来。
继续:
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);
}
这段代码,一般会走else里面,因为if里是针对未来的判断。
首先开辟rw空间,注意是先创建rw,再将临时变量ro设置到rw中,最后再设置cls中bits的data。
ro是clean memory(干净的内存),防止在运行时对其增删改,所以此时只是将ro拷贝到了rw中。
而rwe是在运行时,比如添加关联属性或者添加方法等,才会去创建一个rwe来存放,从而避免了rw的过度开销。
一般情况下,我们从rw读取属性、方法等时,是直接读的ro中的信息。
比如这里就可以看出,会判断是否有ext,有的话从ext中读取(因为rw_ext中绑定了ro),没有的话直接读取ro。
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

通过打印也可以看到,rwe中绑定了ro。
接着往下:
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
这段代码主要是对cls->isa的一些处理,不做重点研究了。
继续:
// 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);
这里就是完成继承链的绑定,然后设置InstanceSize。
回顾在对象的探索中,_class_createInstanceFromZone核心函数的第一步就是获取开辟空间大小, 其中size = cls->instanceSize(extraBytes);就是拿到了 cls->setInstanceSize(ro->instanceSize);所设置的大小。
还有一个小问题,如果cls是元类的话,此时只能p出来地址,因为此时元类还没有处理完毕,无法打印名称。
// 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;
}
这里是系统给类加了一个Cxx的函数,我们平时用class_dump出来的头文件中就有这个函数。
最后再来看下重点:
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
methodizeClass 方法话当前的类。
二、methodizeClass
我们通过上面的探索,已经知道了类的信息从macho中读取,并且设置了ro->rw->rwe。
但是比如我们进行方法慢速查找的过程中,是通过二分查找,而二分查找是需要先进行方法排序的。那么方法排序是怎么排的呢?下面就继续探索methodizeClass函数。
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();
这段代码是将rw、ro、rwe放到一个临时变量中。

接下来:
// 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);
}
这里是处理方法、属性、协议。
我们来看prepareMethodLists函数:
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
runtimeLock.assertLocked();
if (addedCount == 0) return;
if (baseMethods) {
ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
} else if (cls->cache.isConstantOptimizedCache()) {
cls->setDisallowPreoptCachesRecursively(why);
} else if (cls->allowsPreoptInlinedSels()) {
//....省略
}
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*/);
}
}
if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
}
}
其中核心函数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) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
以上代码就是对方法进行排序,那么怎么排序的呢,我们打印一下执行过std::stable_sort之后的mlist:

可以看到,是通过sel的地址升序排序的。
那么sel是怎么来的呢?
SEL sel_registerNameNoLock(const char *name, bool copy) {
return __sel_registerName(name, 0, copy); // NO lock, maybe copy
}
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
通过sel_registerNameNoLock -> __sel_registerName 可以看到。
(1)有一个sel_alloc进行了开辟,所以sel是有内存地址的。
(2)其中namedSelectors这张表就是在_read_images中,sel_init创建的。
(3)search_builtins方法:
static SEL search_builtins(const char *name)
{
#if SUPPORT_PREOPT
if (SEL result = (SEL)_dyld_get_objc_selector(name))
return result;
#endif
return nil;
}
const char* _dyld_get_objc_selector(const char* selName)
{
// Check the shared cache table if it exists.
if ( gObjCOpt != nullptr ) {
if ( const objc_opt::objc_selopt_t* selopt = gObjCOpt->selopt() ) {
const char* name = selopt->get(selName);
if (name != nullptr)
return name;
}
}
if ( gUseDyld3 )
return dyld3::_dyld_get_objc_selector(selName);
return nullptr;
}
因为一些系统的库,比如UIkit,它的sel需要从共享缓存中拿,所以会跳转到dyld中去根据name拿对应的sel。如果是dyld3,那么继续走dyld3中的_dyld_get_objc_selector
const char* _dyld_get_objc_selector(const char* selName)
{
log_apis("dyld_get_objc_selector()\n");
return gAllImages.getObjCSelector(selName);
}
const char* AllImages::getObjCSelector(const char *selName) const {
if ( _objcSelectorHashTable == nullptr )
return nullptr;
return _objcSelectorHashTable->getString(selName, _objcSelectorHashTableImages.array());
}
const char* ObjCStringTable::getString(const char* selName, const Array<uintptr_t>& baseAddresses) const {
StringTarget target = getPotentialTarget(selName);
if (target == sentinelTarget)
return nullptr;
dyld3::closure::Image::ObjCImageOffset imageAndOffset;
imageAndOffset.raw = target;
uintptr_t sectionBaseAddress = baseAddresses[imageAndOffset.imageIndex];
const char* value = (const char*)(sectionBaseAddress + imageAndOffset.imageOffset);
if (!strcmp(selName, value))
return value;
return nullptr;
}
从getString函数中可以看到,会返回地址。
注意:如果此时有分类同名方法的话,会默认根据同名方法的imp进行升序排序。
再回到methodizeClass函数中,执行完方法排序之后,如果rwe有值,再对rwe进行处理。
三、懒加载类和非懒加载类
我们回到调用realizeClassWithoutSwift的地方,可以看到代码注释:Realize non-lazy classes (for +load methods and static instances(暂不关注))
实现一个非懒加载类。在类中添加+(void)load方法之后,就会从懒加载类变成非懒加载类。
因为+(void)load方法是在load_imags的时候就调用了,如果此时没有去加载实现类,那么怎么能够调用+(void)方法呢,所有如果类写了+(void)load方法,它就会提前去加载信息。
我们知道了非懒加载的情况,那么懒加载类是怎么去加载信息的呢?
我们在realizeClassWithoutSwift(Class cls, Class previously)打断点,然后bt查看堆栈

可以看到:lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock
证明懒加载类是在第一次消息发送的时候去加载信息的。
那为什么要区分懒加载和非懒加载呢?
因为加载类信息的时候,会处理很多的方法,创建很多的临时变量,还要进行排序等,比较耗时,如果都放在main函数之前来做,会增加启动的时间。
小结:
懒加载类的情况:
lookUpImpOrForward - > realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass
非懒加载类的情况:
readClass->_getObjc2NonlazyClassList -> remapClass -> realizeClassWithoutSwift -> methodizeClass
四、分类的本质
先定义一个MyClassTest的分类
@protocol TestProtocol <NSObject>
- (void)testProtocol;
@end
@interface MyClassTest (Test) <TestProtocol>
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
- (void)cate_instanceMethod3;
+ (void)cate_classMethod;
@end
@implementation MyClassTest (Test)
- (void)cate_instanceMethod1 {
NSLog(@"%s",__FUNCTION__);
}
- (void)cate_instanceMethod2 {
NSLog(@"%s",__FUNCTION__);
}
- (void)cate_instanceMethod3 {
NSLog(@"%s",__FUNCTION__);
}
+ (void)cate_classMethod {
NSLog(@"%s",__FUNCTION__);
}
@end
通过clang -rewrite-objc MyClassTest-Test.m -o MyClassTest-Test.cpp
编译成c++
//分类:方法 -> attachtoclass
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;
};
static struct _category_t _OBJC_$_CATEGORY_MyClassTest_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClassTest",
0, // &OBJC_CLASS_$_MyClassTest,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClassTest_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyClassTest_$_Test,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MyClassTest_$_Test,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClassTest_$_Test,
};
可以看到category_t的结构。
这里先说一个经典的面试题:为什么分类里面不能直接添加属性呢?
是因为分类里面没有对应的get和set方法,并且没有成员变量。
五、classProperties
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;
}
};
通过clang编译之后,我们再看objc的源码,可以看到category_t的结构将properties分成了instanceProperties 和 _classProperties。
instanceProperties我们很好理解,就是对象属性。但是_classProperties是什么呢?
Objective-C Class Properties 早在 WWDC 2016 中就已经公示,给 Objective-C 加入这个特性主要是为了与 Swift 类型属性相互操作。
@interface MyClassTest : NSObject
@property (nonatomic, copy) NSString *mgName;
@property (class, nonatomic) NSString *version;
- (void)testInstance;
+ (void)classInstance;
@end
@implementation MyClassTest
static NSString* _version = @"0.1.1";
+ (NSString *)version {
return _version;
}
//...
@end
比如我定一个类属性,在内部定义一个静态变量,然后在定一个类的get方法 + (NSString *)version。那么在外部就可以调用类属性读取version。
对于的swift的类型属性:
class SwiftClassText: NSObject {
class var version: String {
return "0.1.1"
}
}
这么做的好处就是,可以方便的读取类的信息,但是需要自己写get/set方法。
看一些文章还说可以方法做组件化解耦合,这里我没有去尝试过,未来可以试试看。