回顾
前面讲到了分类的本质以及rwe
什么时候被赋值,在查找rwe
赋值过程中,发现一个方法 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);
}
我们并不知道,什么时候回调用attachCategories
,由已知方法反推,全局搜索attachCategories
,得到下面两条线,再往前面追溯,分支太多,不好判断
- realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories
- load_categories_nolock->attachCategories
分类主类4种情况的加载流程
在每个方法里面添加下面方法,来判断到底是如何加载
const char *mangledName = cls->nonlazyMangledName();
const char *person = "LKTeacher";
if (strcmp(mangledName, person) == 0) {
printf("********load_categories_nolock**********\n");
}
- 主类和分类都有
load
方法
_read_images非懒加载->realizeClassWithoutSwift->methodizeClass->load_categories_nolock->attachCategories
- 主类有
load
方法,分类没有load
方法
_read_images懒加载->realizeClassWithoutSwift->attachToClass->methodizeClass
- 主类没有
load
方法,分类有load
方法
_read_images懒加载->realizeClassWithoutSwift->attachToClass->methodizeClass
- 主类和分类都没有
load
方法
都没有走
分类的加载流程跟踪
在刚刚的方法里面添加断点,跟踪调试,确认是否类的加载流程是按上述流程执行。 打印 发现分类并没有加载,继续进入下一个断点
进入分类相关加载方法
可以看到编译时,name
变为了分类的名字
可以看到分类的信息已经获取到,而且mlists
最后一位地址就是cls
可以验证上面的流程
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;
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
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();
}
}
else if (!list && addedCount == 1)
很简单,将addedLists[0]
赋值给list
else
,oldList
存在时,oldCount
为1,array()->count
为oldCount + addedCount
,并将oldList
放到array()->lists
最后一个元素,然后顺序将addedLists
元素存放至array()->lists
if (hasArray())
,将addedLists
按顺序插入数组前面
打印
可以看到,是将新分类LKTeacher(LK)
存放在第一个元素。
那什么时候会进入if (hasArray())
。
多个分类的加载
添加多个分类,并在分类里面都添加load
方法
按上面断点调试,第一个分类情况,和上面流程一样,第二个分类进入if (hasArray())
打印 可以看到,最后一个元素为主类。
总结
主类和分类都有懒加载和非懒加载区别
分类和主类只要有一方写了load
方法,主类就会走非懒加载的流程。
如果两个都不写load
方法,那么这些方法将在第一次消息发送的时候由系统通过macho
直接放到data
中。
如非必要,不要写load
方法,特别不建议在分类里面写load
方法。
补充
methodList
的数据结构
打印前面ro
可以看到methodList
的数据结构就是method_list_t *
,查看method_list_t
,
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
//中间代码略
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
uint32_t flags() const {
return entsizeAndFlags & FlagMask;
}
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
//中间代码略
};
可以看到get(0)
方法是通过指针平移的方式来地址的
(lldb) p $3.get(0)
(method_t) $5 = {}
通过get
方法取出来的数据是method_t
,查看method_t
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
private:
bool isSmall() const {
return ((uintptr_t)this & 1) == 1;
}
// The representation of a "small" method. This stores three
// relative offsets to the name, types, and implementation.
struct small {
// The name field either refers to a selector (in the shared
// cache) or a selref (everywhere else).
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
IMP imp(bool needsLock) const {
if (isSmall()) {
IMP imp = remappedImp(needsLock);
if (!imp)
imp = ptrauth_sign_unauthenticated(small().imp.get(),
ptrauth_key_function_pointer, 0);
return imp;
}
return big().imp;
}
//中间代码略
}
method_t
根据不同架构有small
和big
两种情况,通过imp
可以取出struct
里面的imp
。
主类没有实现load
方法,多个分类实现load
方法,数据加载流程
添加多个分类,分类实现load
方法,主类不实现load
方法,运行查看流程,发现实际走了attachCategories
方法,按照前面流程
- 主类没有
load
方法,分类有load
方法
_read_images懒加载->realizeClassWithoutSwift->attachToClass->methodizeClass 可是为什么会走
attachCategories
方法,添加断点查看流程。
发现了一个新方法prepare_load_methods
,这里会迫使加载主类和所有分类(没有任何内容的除外)。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
const char *mangledName = cls->nonlazyMangledName();
const char *person = "LKTeacher";
if (strcmp(mangledName, person) == 0) {
printf("********%s**********\n",__func__);
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
分类的加载是否需要排序
分类的存储是以二维指针的方式存在的,
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
所以是先取出第一个分类,查找方法,然后取出第二个分类,再次查找,直到找到方法为止。所以分类不需要排序。
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
二分查找的while
方法,因为当类和分类都在ro->data
被读取的时候,方法整合到一块
了,所以需要probe–
确保找的是最前面的一个方法,即分类的方法。
class_ro_t
struct class_ro_t {
uint32_t m_flags;
uint32_t m_instanceStart;
uint32_t m_instanceSize;
uint32_t m_reserved;
lldb::addr_t m_ivarLayout_ptr;
lldb::addr_t m_name_ptr;
lldb::addr_t m_baseMethods_ptr;
lldb::addr_t m_baseProtocols_ptr;
lldb::addr_t m_ivars_ptr;
lldb::addr_t m_weakIvarLayout_ptr;
lldb::addr_t m_baseProperties_ptr;
std::string m_name;
bool Read(Process *process, lldb::addr_t addr);
};
bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) {
size_t ptr_size = process->GetAddressByteSize();
size_t size = sizeof(uint32_t) // uint32_t flags;
+ sizeof(uint32_t) // uint32_t instanceStart;
+ sizeof(uint32_t) // uint32_t instanceSize;
+ (ptr_size == 8 ? sizeof(uint32_t)
: 0) // uint32_t reserved; // __LP64__ only
+ ptr_size // const uint8_t *ivarLayout;
+ ptr_size // const char *name;
+ ptr_size // const method_list_t *baseMethods;
+ ptr_size // const protocol_list_t *baseProtocols;
+ ptr_size // const ivar_list_t *ivars;
+ ptr_size // const uint8_t *weakIvarLayout;
+ ptr_size; // const property_list_t *baseProperties;
DataBufferHeap buffer(size, '\0');
Status error;
process->ReadMemory(addr, buffer.GetBytes(), size, error);
if (error.Fail()) {
return false;
}
DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(),
process->GetAddressByteSize());
lldb::offset_t cursor = 0;
m_flags = extractor.GetU32_unchecked(&cursor);
m_instanceStart = extractor.GetU32_unchecked(&cursor);
m_instanceSize = extractor.GetU32_unchecked(&cursor);
if (ptr_size == 8)
m_reserved = extractor.GetU32_unchecked(&cursor);
else
m_reserved = 0;
m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
m_name_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor);
m_ivars_ptr = extractor.GetAddress_unchecked(&cursor);
m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor);
DataBufferHeap name_buf(1024, '\0');
process->ReadCStringFromMemory(m_name_ptr, (char *)name_buf.GetBytes(),
name_buf.GetByteSize(), error);
if (error.Fail()) {
return false;
}
m_name.assign((char *)name_buf.GetBytes());
return true;
}