1. 回顾
在上篇博文中,已经从dyld
到_objc_init
再到read_images
整个流程串联起来了,最后定位到了类的初始化是在realizeClassWithoutSwift
中,本篇博文将深入分析类的加载,请搬好板凳坐下认真往下看。
iOS底层探索之类的加载(一):read_images分析
2.realizeClassWithoutSwift
在read_images
流程中,会对类进行一些修复工作,同时会将类的名称与类进行关联,插入对照表中,并更新到内存中的类表。
rw
和ro
的处理我们还不得而知。那么编译生成的MachO
文件中类的相关信息,是何时插入到内存对应的cls
中的呢?
// 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);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
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);
}
}
从read_images
源码中的部分注释可以知道,这是针对非懒加载的类进行初始化操作。那么什么叫做非懒加载的类呢?就是实现了+load方法
。
- 非懒加载的类实现了
+load方法
。 - 通过
nlclslist
函数获取非懒加载类列表。 - 对类进行
递归
处理,完成非懒加载类
的初始化工作。 -
addClassTableEntry
把类添加到内存表中。 -
realizeClassWithoutSwift
初始化类。
2.1 realizeClassWithoutSwift源码
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
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?
const char * className = "JPStudent";
if (strcmp(class_getName(cls), className) == 0)
{
printf("hello JPStudent...");
}
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;
}
2.2 ro、rw的处理
从machO
中获取的数据地址,根据class_ro_t
格式进行强转,同时初始化rw
的空间,并复制一份ro
的数据放入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();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
-
ro
属于clean memory
,在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息; -
rw
的数据空间属于dirty memory
,rw
是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。
具体可以去看看wwdc2020里面做了很详细的说明和分析。
2.3 类的处理
父类和元类的处理
// 递归,加载父类、元类的实现
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
对于是否支持NONPOINTER_ISA
的类进行处理,指针优化是指Isa
的末尾位是1
。
对于元类以及特殊情况下的场景的一些类,无需开启指针优化的类,使用Raw Isa,Isa
的末尾位是0
。
#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
是否纯指针。元类
isa
是纯指针。类的
isa
是否纯指针取值flags
第13位。父类是纯指针,并且父类还有父类。那么自己也要是纯指针。
rawIsaIsInherited
(只是控制打印)表示继承的是纯指针。递归设置
isa
为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。
关联父类与元类
//关联父类与元类。也就是继承链与isa走位。
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
2.3 代码调式
添加如下代码,定位调式
const char * className = "JPStudent";
if (strcmp(class_getName(cls), className) == 0)
{
printf("hello JPStudent...");
}
设置断点,分别判断在ro
初始化前、后,进行lldb
调式打印ro
的数据结构的变化。
-
ro
赋值前打印:
hello JPStudent...(lldb) p cls
(Class) $3 = 0x00000001000084e0
(lldb) p cls
(Class) $4 = JPStudent
(lldb) p ro
(const class_ro_t *) $5 = 0x00007ffeefbff190
(lldb) p *$5
(const class_ro_t) $6 = {
flags = 4022333872
instanceStart = 32766
instanceSize = 0
reserved = 0
= {
ivarLayout = 0x0000000100008508 "\xe0\x84"
nonMetaclass = JPStudent
}
name = {
std::__1::atomic = "\xe0\x84" {
Value = 0x0000000100008508 "\xe0\x84"
}
}
baseMethodList = 0x00007ffeefbff1e0
baseProtocols = 0x00000001003269a0
ivars = 0x00000001000084e0
weakIvarLayout = 0x0000000100008508 "\xe0\x84"
baseProperties = 0x000000010036d080
_swiftMetadataInitializer_NEVER_USE = {}
}
-
ro
赋值后打印:
(lldb) p ro
(const class_ro_t *) $9 = 0x00000001000081b8
(lldb) p *$9
(const class_ro_t) $10 = {
flags = 0
instanceStart = 8
instanceSize = 24
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic = "JPStudent" {
Value = 0x0000000100003d2d "JPStudent"
}
}
baseMethodList = 0x0000000100008200
baseProtocols = nil
ivars = 0x0000000100008340
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008388
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $10.baseMethodList
(void *const) $11 = 0x0000000100008200
(lldb) p *$11
(lldb)
通过调式打印,并没有打印出方法列表,但是我JPStudent
是有方法的,说明此时,只是有了ro
的数据结构,只是个空的结构的地址,sel
和imp
还没有绑定起来。
在
realizeClassWithoutSwift
中最后调用了如下代码,根据注释可以看到应该是对分类的处理,参数cls
没问题,previously
是从_read_images
中传过来的为nil
// Attach categories
methodizeClass(cls, previously);
那么现在就对methodizeClass
分析
3. methodizeClass分析
3.1 methodizeClass源码
/***********************************************************************
* 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
}
-
ro 、rw、rew
初始化,元类判断标记 - 获取方法列表
- 获取属性列表
- 获取协议列表
- 是否根元类,根元类加了
initialize
方法 - 分类的处理
在methodizeClass
方法进行断点调试,发现rwe
为空,说明此时还没有对类进行相关的扩展操作,所以rwe
还没有被创建初始化。那么此时我们对方法、属性、协议的添加操作也是无效的。
3.2 prepareMethodLists
在method_list_t
处理时调用prepareMethodLists
了方法,核心代码如下:
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
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*/);
}
}
addedCount
值为1
,addedLists
为**
类型,就是指针的指针类型。那么mlist
就是ro
的list
。如果没有排序则修正,再排序
ro
的methodLists
。-
fixupMethodList
对
method
的name
也就是SEL
进行了修正,也就与慢速消息查找的二分查找对应上了。
- 排序验证
通过以下代码对 排序前 和排序后 进行方法的打印
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
printf("排序后:%s---%p\n",name,meth.name());
}
奇怪的是排序前和排序后的顺序是一样的,我猜测有可能是顺序已经排序好了,无需要再排序,或者是添加的时候就已经是排好了。
4.分类的探索
在prepareMethodLists
执行完成后是没有rwe
数据,所以后续的attachLists
相关操作都不会执行。根据之前WWDC
的介绍rwe
在有分类的情况下会出现,那么就去建立一个分类看看。
4.1 分类底层结构
通过clang
查看分类结构
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
的名字应该是分类名称。 -
cls
指向类。 - 在类中只有一个
methods
,在分类中有了instance_methods
与class_methods
。因为分类没有元类(也就是没有分元类)。 - 分类中是有
properties
的。
通过xcode
帮助文档也可以查看
再去
objc
源码里面搜索objc_category
验证一下
还真有哦!所以也验证了分类的底层是结构体!
4.2 分类源码探索
通过对methodizeClass
源码的分析,分类的处理核心逻辑在attachLists
与attachToClass
中。rwe
的赋值是来源于rw->ext()
代码如下:
auto rwe = rw->ext();
-
ext()
代码如下:
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
//获取rwe
auto v = get_ro_or_rwe();
if (fastpath(v.is())) {
return v.get(&ro_or_rw_ext);
} else {
//创建rwe
return extAlloc(v.get(&ro_or_rw_ext));
}
}
在extAllocIfNeeded
中进行了rwe
的创建。通过在objc
源码里面搜索extAllocIfNeeded
的调用,发现了在attachCategories
方法里面的调用比较符合。而attachCategories
的调用逻辑在attachToClass
与load_categories_nolock
中。
由此所以分类的加载就有了两条线路:
methodizeClass --> attachToClass --> attachCategories
load_images --> loadAllCategories --> load_categories_nolock --> attachCategories
5.总结
- 懒加载类与⾮懒加载类: 指当前类是否实现
load
⽅法 - 非懒加载类情况
map_images
的时候 加载所有类数据_getObjc2NonlazyClassList --> readClass -- > realizeClassWithoutSwift --> methodizeClass
- 懒加载类情况是数据加载推迟到第⼀次消息的时候,
lookUpImpOrForward --> realizeClassMaybeSwiftMaybeRelock -- > realizeClassWithoutSwift --> methodizeClass
更多内容持续更新
喜欢就点个赞吧
觉得学习到了的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我
欢迎大家留言交流,批评指正,互相学习,提升自我