15.iOS底层学习之类的加载分析
上一篇文章学习了read_images相关的流程,发现类的初始和方法realizeClassWithoutSwift有关系。这篇文章讲重点分析realizeClassWithoutSwift里面的具体操作,主要去学习以下几个我自己比较关心的问题:
1、realizeClassWithoutSwift的主要功能
2、rw是什么时候被赋值的?
3、懒加载类和非懒加载类的区别
4、分类的本质是?结构?
5、分类中的方法是如何被加载的?
realizeClassWithoutSwift
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class 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;
//cls不存在不往下执行了,返回nil
if (!cls) return nil;
//判断cls是不是被实现过了(可能有别的线程调用过实现这个类),为了防止并发实现过了这个类,要加锁
// Locking: To prevent concurrent realization, hold runtimeLock.
if (cls->isRealized()) {
//去验证下被实现过的类,内部进行了是不是有脏数据添加进去的判断。
validateAlreadyRealizedClass(cls);
return cls;
}
// fixme verify class is not in an un-dlopened part of the shared cache?fixme verify类不在共享缓存的未打开部分中
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数据已经被分配
rw = cls->data(); //直接读取rw数据
ro = cls->data()->ro();//直接读取ro数据
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中的ro
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;//赋值标志位
cls->setData(rw);//rw赋值
}
//初始化或者清空cache
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
//为此类选择一个索引。
//如果索引不再可用,则设置cls->instancesRequireRawIsa
cls->chooseClassArrayIndex();
.....
// Realize superclass and metaclass, if they aren't already.实现超类和元类,如果它们还没有实现的话。
// This needs to be done after RW_REALIZED is set above, for root classes.这需要在上面为根类设置RW_之后完成。
// This needs to be done after class index is chosen, for root metaclasses.对于根元类,这需要在选择类索引之后完成。
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
......
//省略这个部分是一些关于SUPPORT_NONPOINTER_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.这可能会重新分配类,更新我们的ro变量
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 将一些标志从ro复制到rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// 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
方法的阅读,大致了解了它的主要功能:
·完成了rw的赋值,cls->setData(rw);
·初始化或者清空了cache,cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
·实现了父类和元类, cls->setSuperclass(supercls); cls->initClassIsa(metacls);
·将该类连接到其父类的子类列表 ,addSubclass(supercls, cls);
·关联分类,methodizeClass;
懒加载类和非懒加载类
在学习read_images的时候里面有关于非懒加载类的注释说明,我们来详细看看懒加载类和非懒加载类有什么差别。
non-lazy classes
这段注释下面的代码如下(这是截取方法read_images的部分代码):
// 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);
}
}
根据上面代码的注释,实现非懒加载类(实现了+load方法和静态实例的)。也就是说一个类实现了+load方法会走这个非懒加载的类的的实现。我们通过断点结合+load的方法实现与否可以验证这段代码的执行和+load确实有关系。
了解了非懒加载类,那么接下来研究懒加载类是个什么东西和加载流程。根据+load的实现与否来看,实现了是非懒加载,那么没实现的应该就是懒加载类。我们去掉+load来进行调试研究下懒加载类的调用过程。
因为是不是懒加载的类都会走方法realizeClassWithoutSwift,所以我们通过调试方法realizeClassWithoutSwift,发现realizeClassWithoutSwift的调用是由lookUpImpOrForward发起调用的,通过realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift这个过程来调用。
所以我们可以了解到,懒加载的类是在第一次被使用到的时候被加载到映射表里。
·非懒加载类调用过程:
map_images
map_images_nolock
_read_images
realizeClassWithoutSwift
而在_dyld_objc_notify_register调用的时候还有一个load_images,会去调用load方法,流程是:
load_images
hasLoadMethods
_getObjc2NonlazyClassList
prepare_load_methods
call_load_methods
·懒加载类调用过程,是在类第一次被使用的时候调用:
lookUpImpOrForward
realizeAndInitializeIfNeeded_locked
realizeClassMaybeSwiftAndLeaveLocked
realizeClassMaybeSwiftMaybeRelock
realizeClassWithoutSwift
分类
在上面的方法realizeClassWithoutSwift关于分类的部分是最后的分类关联,方法methodizeClass
。
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();
// Install methods and properties that the class implements itself.
//🍎方法列表的获取,然后⚠️attachLists
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
//🍎属性列表的获取,然后⚠️attachLists
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
//🍎协议列表的获取,然后⚠️attachLists
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.
//🍎关联分类,attachToClass
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);
}
通过方法注释了解到Fixes up cls's method list, protocol list, and property list.:会修复类的方法列表
,协议列表
还有属性列表
,会关联上分类。
上面源代码中有几个关键的方法来简单的介绍下功能:
prepareMethodLists
:该方法会调用fixupMethodList->stable_sort,而stable_sort会根据传进来的第三个参数进行排序。
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
SortBySELAddress
是一个结构体,里面有关于method_t.big的操作,而big的结构如下:
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
struct SortBySELAddress :
public std::binary_function<const struct method_t::big&,
const struct method_t::big&, bool>
{
bool operator() (const struct method_t::big& lhs,
const struct method_t::big& rhs)
{ return lhs.name < rhs.name; }
};
name是SEL类型的,所以是根据SEL进行排序,lhs.name < rhs.name
Lhs :Left Hand Side,左边
Rhs : Right Hand Side,右边
所以就是从左到右由小到大排列。
attachLists
:有两个参数,List* const * addedLists, uint32_t addedCount,这是个addedLists增加addedCount个元素的操作,大致的流程是:
-addedCount为0,不执行后面的操作直接返回;
-如果旧的list和要新增的addedCount都不为空(many lists -> many lists),那么开始执行增加流程:
1、记录旧list的长度,新list的长度等于旧的+ addedCount;
2、创建一个新的array空间大小是旧array长度+ addedCount个元素大小,长度为旧array长度+ addedCount,旧数组的长度也更新为最新的长度;
3、倒序遍历旧数组,倒序插入到新数组,然后遍历到0把新插入的addlist的内容插入到最前面。(这个地方相当于把旧数组在新数组的位置整体往后平移了addedCount个,然后再把addedLists中的数据从0开始一次添加到新数组的第addedCount,这个操作意味着,后添加进来的会在前边,有点儿像入栈操作😱)
4、旧数组释放,新数组set。
-旧list为空且addcount为1( 0 lists -> 1 list),直接oldList的首地址指向addlist的首地址;
-旧list为1且addcount为大于1的时候(1 list -> many lists),oldList的一个元素直接拼接到lists的addcount位置,然后遍历addedLists赋值给lists,从0开始一直到addedCount
这个过程有点多,我还是放一下相关的程序源码吧:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return; //为0 直接返回
if (hasArray()) {
// many lists -> many lists 多对多
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
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 0到1
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists 1到多
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();
}
}
hasArray()
union {
Ptr<List> list;
uintptr_t arrayAndFlag;
};
bool hasArray() const {
return arrayAndFlag & 1;
}
hasArray()是arrayAndFlag与1的一个操作,而arrayAndFlag的值是在setArray中进行的,所以setArray之后不释放就是真,没set之前就是假false。
attachToClass
:关联分类,里面调用了attachCategories,这方法还是比较重要的,放一下源码。
attachCategories
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
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();
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->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){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
从分类关联方法列表,协议,属性到一个类上,假如所有的类都被如期加载进来了那么分类的顺序就是加载的顺序,最旧的分类是第一个被加载的。
这个里面分别获取了分类的method_list_t,property_list_t,protocol_list_t过程和methodizeClass差不多,只是methodizeClass是从ro
里获取,而这里是从rwe
获取,并且通过方法attachLists,上面我们分析过了方法attachLists,后调用的会在相应的表的最前边。
以上是分类相关属性添加到类的相关表里的操作,接下来我们来看看分类的结构,我们添加一个分类,然后 xcrun -sdk iphonesimulator clang -rewrite-objc main.m一下,看到了category_t
。
category_t
category_t.png由此可以知道它的结构:
-分类是结构体类型;
-name分类的名字;
-cls就是分类指向的类;
-在类中只有一个methods,在分类中有了instance_methods与class_methods。
-分类中是有properties的,只是分类的properties没有set和get方法。
分类中的方法是如何被加载的
我们前边已经分析过,最终是调用attachCategories
,总结下流程就是:
-_dyld_objc_notify_register->map_images->map_images_nolock-> read_images->realizeClassWithoutSwift->methodizeClass->attachToClass-> attachCategories
-_dyld_objc_notify_register->load_images->loadAllCategories->load_categories_nolock-> attachCategories
-
_dyld_objc_notify_register->load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass-> attachCategories
调用示意图如下:
分类加载示意图.jpg