iOS 类的加载
1、懒加载类与非懒加载类
官方在对类进行处理的时候, 为了提高对类处理的效率以及性能, 就对类进行了识别, 当类需要使用的时候, 系统才会对类进行实现. 如果没有使用就不会实现. 当需要实现才进行加载的类就被称为懒加载类. 反之无论是否使用到这个类, 都对这个类进行加载的类就被称为非懒加载类.
在 _read_images()
中, 当对类进行处理的时候, 通过 _getObjc2NonlazyClassList()
获取到的类中并没有我们自己创建的类. 这就说明我们平常通过XCode创建的类默认为是懒加载类.
我们知道 +(void)load
方法会在 main()
之前调用. 我们在自己的类中实现 +(void)load
方法:
+ (void)load{
NSLog(@"load");
}
复制代码再来 _getObjc2NonlazyClassList()
中就可以找到自己的类.
这样的话意味着当一个类实现了+(void)load
方法后, 它就会由 懒加载类
变成 非懒加载类.
2. iOS 类的加载
由于 objc_init
是 runtime
入口(runtime还没有处理类的元数据)。
首先简单看一个runtime初始化的流程。
-
①.
objc_init
-
②.
read_images
读取所有镜像文件信息- 初始化全局
class map
,读取macho
的__objc_classlist
初始化全局class map
,__objc_classrefs
。
- 初始化全局
- 读取 __objc_selrefs等段读取相关。进行
sel fix up
- 读取 __objc_selrefs等段读取相关。进行
- 读取
protocol
- 读取
- 读取所有 非懒加载 类,并调用
realizeClass
进行初始化,进一步调用methodizeClass(Attach categories)
- 读取所有 非懒加载 类,并调用
- 读取
categories
,把读取出来的categories
放到全局的map
里,如果cat
关联的cls
已经被realize
则进行remethodizeClass
- 读取
-
③. 对所有非懒加载的类 和
category
执行+load
。
类的可读写元数据的初始话主要发生在 realizeClass
和 methodizeClass
在dyld和ObjC的关联中我们知道当 dyld
加载到开始链接主程序的时候,最终会走到类的加载方法methodizeClass
。
通过 mangledName 筛选出我们所定义的类的实现,定位到当前类
在methodizeClass中,从ro中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给rw
/***********************************************************************
* 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();
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
// if (!kc_isMeta) {
// printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
// }
}
// 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));
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
}
attachLists
是如何插入数据的呢?方法属性协议都可以直接通过 attachLists
插入吗?
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isUniqued() const;
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
ASSERT(i < count);
return i;
}
};
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct protocol_list_t {
// count is pointer-sized by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
size_t byteSize() const {
return sizeof(*this) + count*sizeof(list[0]);
}
protocol_list_t *duplicate() const {
return (protocol_list_t *)memdup(this, this->byteSize());
}
typedef protocol_ref_t* iterator;
typedef const protocol_ref_t* const_iterator;
const_iterator begin() const {
return list;
}
iterator begin() {
return list;
}
const_iterator end() const {
return list + count;
}
iterator end() {
return list + count;
}
};
方法、属性继承于 entsize_list_tt
,协议则是类似 entsize_list_tt
实现,都是二维数组

void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;//10
uint32_t newCount = oldCount + addedCount;//4
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;// 10+4
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
从 attachLists
的源码实现中可以得出:
-
(多对多)如果当前调用
attachLists
的list_array_tt
二维数组中有多个一维数组- 通过
realloc
对容器进行重新分配大小为原来的大小加上新增的大小 - 通过
memmove
把原来的数据移动到容器的末尾 - 把新的数据
memcpy
拷贝到容器的起始位置
- 通过
-
(0对一)如果调用
attachLists
的list_array_tt
二维数组为空且新增大小数目为 1- 直接赋值
addedList
的第一个list
- 直接赋值
-
(一对多)如果当前调用
attachLists
的list_array_tt
二维数组只有一个一维数组- 通过
realloc
对容器进行重新分配大小为原来的大小加上新增的大小 - 由于只有一个一维数组,所以直接赋值到新
Array
的最后一个位置 - 把新的数据
memcpy
拷贝到容器的起始位置
- 通过
而 memmove
和 memcpy
的区别在于:
- 在不知道需要平移的内存大小时,需要memmove进行内存平移,保证安全
- memcpy从原内存地址的起始位置开始拷贝若干个字节到目标内存地址中,速度快
同样在 methodizeClass
方法中也会执行到 attachToClass
方法,在 attachToClass
方法中也会执行 attachCategories
方法
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
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);
auto rwe = cls->data()->extAllocIfNeeded();
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
}
}
// mlists -> 二维数组
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);
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);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
一、主类实现 load
方法, 其中一个分类实现 load
方法,一个没有实现 load
方法 :非懒加载类、懒加载分类
只要有一个分类是 非懒加载分类, 所有都会是非懒加载分类

二、主类实现 load
方法, 分类没有实现 load
方法 : 非懒加载类、懒加载分类
主类实现
load
方法,分类的方法来自于主类的data()
->(const class_ro_t *)cls->data()
,编译时期完成data()
当非懒加载类与懒加载分类,此时非懒加载类也会在程序启动时加载进内存。

三、主类没有实现 load
方法, 分类没有实现 load
方法 :懒加载类、懒加载分类
第一次消息的时候,分类的方法也会来自于主类的
data()
->(const class_ro_t *)cls->data()
,编译时期完成data()
- 当懒加载类发送消息时,才会断点成功。在
lookupimporforward
里面就做了一次判断是否为懒加载类 -
realizeClassMaybeSwiftMaybeRelock
中区分是否为Swift
-
realizeClassWithoutSwift
跳转到methodizeClass
将类加载到内存中 - 当分类没有实现
load
方法的时候,都是编译时处理, 直接存放进ro
中
整体流程为:msgSend -> lookupimporforward -> realizeClassWithoutSwift -> methodlizeClass

四、主类没有实现 load
方法, 分类实现 load
方法 懒加载类、非懒加载分类
迫使类成为非懒加载类样式来提前加载数据
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);
}

