1、什么是类?
类在面向对象编程中是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。对于 Object-C 来说,类中除了属性和方法还放了协议等。
2、什么是类的加载
在看接下来的内容时,如果你对 App 的启动流程不理解的话,建议看看 App启动流程之 dyld 探析。
打开 mach-o 文件后发现,类是在编译时就确定地址的,只不过是一个相对地址,需要在运行时通过 ASLR 的修正才能拿到正确的内存地址。
类的加载是把修正后的类地址加入到内存中,并且将类的地址和类的名称一一对应,将类中的属性、协议、方法等进行读取和保存到哈希表中,方便下次调用。
从 App启动流程之 dyld 探析 知道,类是在 App 启动的时候回调 _dyld_objc_notify_register(&map_images, load_images, unmap_image); 方法进行加载的,本文研究对象就是这些如何被苹果落实的。
map_images:将类的地址、类和分类中的属性、协议等映射到哈希表中;load_images:主要调用类和分类的load方法。
3、map_images
经过 map_images -> map_images_nolock -> _read_images 的源码查看,核心代码主要在 _read_images 中。
map_images_nolock 源码计算了 mach_header 中的 count,传给了 _read_images 函数。
主要源码如下:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
static bool doneOnce;
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
//判断是否禁用 TaggedPointers
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
// 根据资源总数确定开辟空间数
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
/// 创建保存类的哈希表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
// 方法的加载的修复
static size_t UnfixedSelectors;
{
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
// 将类的列表从 mach-o 中读取出来
for (EACH_HEADER) {
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
// 取出系统的类和自己创建的类
Class cls = (Class)classlist[i];
// 将类插入到 gdb_objc_realized_classes 表中,将名称对应
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
// ...
// 如果当前处理后的类和原始类不一样就需要重新初始化一下
}
}
}
ts.log("IMAGE TIMES: discover classes");
// Fix up remapped classes
// 修复重映射的类
// ...
ts.log("IMAGE TIMES: remap classes");
// 加载协议
for (EACH_HEADER) {
// ...
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
// Fix up @protocol references
// 修复@protocol引用
// ...
ts.log("IMAGE TIMES: fix up @protocol references");
// 发现类别。只有在完成了初始的类别附件后才能这样做。
// 对于在启动时出现的类别,发现延迟到对_dyld_objc_notify_register的调用完成后的第一次load_images调用。
// rdar: / /问题/ 53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
ts.log("IMAGE TIMES: discover categories");
// 加载非懒加载类,用于实现了 +load 方法 和 静态实例
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);
// 实现类
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
// 未来类的加载
// ...
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// ...
#undef EACH_HEADER
}
通过上方代码的分析,发现 map_images 的主流程主要干了以下事情:
- 加载所有类到
gdb_objc_realized_classes表中; - 对所有类做重映射;
- 将所有 SEL 都注册到
namedSelectors表中; - 修复函数指针遗留;
- 将
Protocol都添加到Protocol_map表中; - 对所有
Protocol做重映射; - 对已经初始化的类加载分类,未初始化的类延迟到
load_images; - 初始化所有非懒加载类,对
ro进行排序,包括当前非懒加载类的所有父类和元类; - 处理所有的非懒加载类
Category,包括Class和Meta Class。
4、readClass
如果对于当前从 mach-o 中读取的类,readClass 仅仅是把类的名称读取出来,把类的地址和名称插入 gdb_objc_realized_classes 表中,但是对于 popFutureNamedClass ,这里是会读 ro 写 rw 的。
源码如下:
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
// ...
Class replacing = nil;
if (mangledName != nullptr) {
if (Class newCls = popFutureNamedClass(mangledName)) {
// 对于 popFutureNamedClass 会进行 读ro 写 rw ,但是普通从 mach-o 读取的不会
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
if (headerIsPreoptimized && !replacing) {
// ...
} else {
if (mangledName) {
// some Swift generic classes can lazily generate their names
// 一些快速的泛型类可以惰性地生成它们的名称
//将类和类名称关联
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
}
// 在插入表中一次,防止漏插,插入的就不会重复插入
addClassTableEntry(cls);
}
return cls;
}
5、realizeClassWithoutSwift
实现非懒加载的类,不包含 Swift ,为什么说只是实现非懒加载类呢?
开发中,将一些可能初始化用不到的属性或者对象懒加载以节约内存,当第一次使用属性或者对象的时候再初始化,对于类也是同样道理。
懒加载类和非懒加载类的区别方式:是否实现
+(void)load {}方法。
源码如下(添加了注释):
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<class_rw_t>();
// 把 ro 给 rw
rw->set_ro(ro);
// 类已经开始实现但还没有完成它
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
// 给类的data.bits 赋值
cls->setData(rw);
}
//初始化为空或在伪装中预优化
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
// 非正规化的RO_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();
// 初始化父类
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.
// 元类不需要任何来自非指针ISA的特性
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) {
// 将这个类及其所有子类标记为需要原始isa指针
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
// 设置父类
cls->setSuperclass(supercls);
// 初始化isa
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// 调和实例变量的偏移量。
// This may reallocate class_ro_t, updating our ro variable.
// 可以重新分配class_ro_t,更新ro变量。
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
// 如果还没有设置fastInstanceSize,则设置它。
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.
//从ro或超类传播相关的对象禁用标志
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 的主流程主要干了以下事情:
class_rw_t *rw开辟空间;- 将
class_ro_t *ro赋值给class_rw_t *rw中的ro; - 标记当前类的状态,类已经开始实现但还没有完成它;
- 将
rw给cls的data.bits; - 如果子类是非懒加载的,那么其元类、父类、父元类都需要递归初始化;
- 将子类和父类进行双向链接;
- 元类初始化
isa禁用nonpointerIsa; - 从
ro或superclass集成相关的对象禁用标志; - 条理化类、加载分类。
6、methodizeClass
1、class_rw_ext_t *rwe
WWDC20 之前苹果为了方便开发者使用runtime 的 API 对类的 rw 进行修改,在每个类进行初始化的时候都会将 class_ro_t *ro 的 方法列表、协议 和 属性 等复制一份到 rw 中去。如下图(来自这篇博客):
但是苹果经过数据调研发现,大多 App 中 90% 的类都没有对 class_rw_t *rw 进行操作过。
所以从 WWDC20 后,苹果将 class_rw_t 进行了优化和拆分,把方法列表、协议 和 属性 等单独申请了一个 class_rw_ext_t *rwe ,只有在使用的时候才会初始化它,减少占用内存,优化内存。优化后如下图:
也就是说,从 WWDC20 如果没有对类进行动态添加,取值的时候就直接从 class_ro_t *ro 进行获取了,有了上面这个解释,下面的有些地方就容易理解了。
之前看过 objc-752 记得会把
ro的东西复制到rw,看了半天rwe怎么都是空的,就这里坑了好久,以为自己记错了,Google 后发现这里2020年修改了,整个人 猝!
2、methodizeClass
这里在研究自己写的类的时候,没有给类动态添加方法,这里的 class_rw_ext_t *rwe 都为 NULL。只有 rwe 有值的时候才会从 ro 的 方法列表、协议 和 属性 等复制到 rwe 。
源码如下:
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
// 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);
}
3、prepareMethodLists
判断类中的方法是否需要排序,排序是通过 fixupMethodList 排序的。
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.
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*/);
}
}
// ...
}
4、fixupMethodList
将 selector 的地址进行排序。
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));
}
}
// Sort by selector address.
// 将 selector 的地址进行排序。
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();
}
}
7、分类加载
1、attachToClass 方法
将分类中的 方法列表、协议 和 属性 添加到主类里。
这个方法只有在动态给分类添加的时候才能进来。
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
// 判断元类
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
// 添加实例方法
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
// 附加类方法
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
// 附加实例方法
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
2、attachCategories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
// ...
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
// 初始化 rwe
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
// rwe 调用 attachLists 添加方法
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 添加属性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
// 添加协议
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
// 排序
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
// 进行内存平移操作
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
3、attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 保存之前数组的大小
uint32_t oldCount = array()->count;
// 计算新数组的大小
uint32_t newCount = oldCount + addedCount;
//开辟新的 newCount 的空间
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
// 重新赋值 count
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
// 把旧数组中的方法按照顺序,放到新数组后方
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
//把新数组中的方法放到前方
newArray->lists[i] = addedLists[i];
// 释放旧数组
free(array());
// 设置新数组
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
// 如果没有原来的数组,那么这个数组就是分类数组,直接开辟空间并存储
Ptr<List> oldList = list;
//
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
// 设置数组以便于下一次判断
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList)
array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
在 attachLists 方法中,看到一个重要信息,在给主类添加分类方法的时候是将旧数组中的方法列表后移,而新的方法列表在前。
根据编译流程,1 个主类有 10 个分类的话,那么第 10 个分类的方法同名方法先执行,后续的就不执行了。是因为在方法查找的时候,一旦查找到了,就直接返回了,而不是最后的分类方法覆盖前面的所有同名方法,只是不找了而已。
8、类和分类的搭配使用
| 类型 | 分类 实现了 load | 分类 未实现 load |
|---|---|---|
| 主类 实现了 load | load_images加载分类 | 编译时将分类编译到主类中 |
| 主类 未实现 load | 迫使主类先初始化,load_images加载分类 | 编译时将分类编译到主类中 |
1、非懒加载类+非懒加载分类
这个是最正常的流程,但是是开发中基本不会出现的流程。
源码调用流程如下:
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists。
2、懒加载类+非懒加载分类
开发中处理 method_swizzing 经常使用这个流程,因为分类非懒加载,所以迫使主类提前加载,走的上一个流程。
3、非懒加载类+懒加载分类
主类在 read_images 中 通过 realizeClassWithoutSwift 实现,分类的方法在编译时直接编译到主类中。
4、懒加载类+懒加载分类
主类第一次调用的时候在 lookUpImpOrForward 时实现,分类的方法在编译时直接编译到主类中。
9、load_images
如果分类实现了 load 方法,loadAllCategories 就会被调用,将分类中的方法加载到主类中去。
然后调用主类 load 在调用分类 load。
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
// 如果分类 实现了 load 就会调用,将分类中的方法加载到主类中去
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)
// 调用所有实现了 load 方法的类
call_load_methods();
}
这里有一个问题:如果有人问主类和分类实现了同名方法,执行谁?
答:如果同名方法为 load 方法,先执行主类,后执行分类。如果其他方法,按照编译顺序,实现最后一个分类的同名方法,因为新添加的分类方法在当前类的方法的最前方,方法查找时,找到第一个就不再往后查找了。
load 方法,先执行主类,后执行分类的原因如下:
上方看到了 load_images 的实现的方法,只有 didCallDyldNotifyRegister 为 true 的时候才能执行 loadAllCategories() 方法,但是 didCallDyldNotifyRegister 设置为 true 是在 objc_init _dyld_objc_notify_register(&map_images, load_images, unmap_image); 方法之后,
所以先执行了主类方法。
void _objc_init(void)
{
// ...
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
10、总结
到这里看类的加载就结束了,研究完类的加载这里,有一些小启发:
-
主类和分类能不实现 load 方法就不实现 load 方法,影响启动时间;
-
分类能不实现 load 方法就不实现 load 方法,影响启动时间;
-
如果继承链比较长,子类最好不要实现 load 方法,因为子类实现了
load方法,那么当前继承链的类都要被实现。
感谢阅读,如果对你有帮助,希望给个赞,如果有错误的地方,希望大佬指出,共同学习,一起进步,谢谢。