重学OC第十三篇:类的加载(下)
程序员文章站
2024-01-14 21:13:16
...
一、分类的本质
通过clang命令把分类的.m转为c++的.cpp可从中得出_category_t的结构体,对应于objc源码为
struct category_t {
const char *name; //分类名
classref_t cls; //归属的类
struct method_list_t *instanceMethods; //分类中的实例方法
struct method_list_t *classMethods; //分类中的类方法
struct protocol_list_t *protocols; //协议
struct property_list_t *instanceProperties; //实现属性
struct property_list_t *_classProperties; //类属性
}
从.cpp中也可以看到分类中的属性虽在属性列表中存在,但是方法列表中并没有实现。可知分类的属性只是声明,需要自己去实现setter/getter。
Xcode中也会有警告提示。
二、分类加载
2.1 方式一
在分析map_images方法源码的时候,在懒加载代码前有一段关于分类的代码
//仅在完成初始分类附件后才执行此操作。对于启动时出现的类别,将发现推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用之后。
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
从注释中的信息找到load_images中以下代码
//didInitialAttachCategories初始为flase,didCallDyldNotifyRegister在objc_init最后初设为了true
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// 分类的主类丢失(可能是弱链接),忽略该分类。
continue;
}
// Process this category.
if (cls->isStubClass()) {
//桩类不会实现。在初始化之前,桩类才知道其元类,
//因此我们必须将具有类方法或属性的分类添加到桩本身。
//methodizeClass()将找到它们并将它们添加到适当的元类中。
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// 首先,用目标类注册分类。
// 然后,如果实现了该类,则重建该类的方法列表(等)。
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
//开始调用processCatlist处理__objc_catlist、__objc_catlist2中的分类
processCatlist(_getObjc2CategoryList(hi, &count));
processCatlist(_getObjc2CategoryList2(hi, &count));
}
void addForClass(locstamped_category_t lc, Class cls)
{
runtimeLock.assertLocked();
//lc{cat, hi} lc中存有分类和头信息
auto result = get().try_emplace(cls, lc);
if (!result.second) {
result.first->second.append(lc);
}
}
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
//查找适合Key的存储桶,并存在在TheBucket中。如果存储桶包含键和值,则返回true,否则返回带有空标记或tombstone(墓碑、逻辑删除)的存储桶并返回false。
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // 已经在map表
// map表中没有,就插入新元素。
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
2.2 方式二
在上一篇中的methodizeClass方法中还看到了
objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
这段代码把分类附加到类中的。
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();
//以previously为key查找存储桶,找到返回以找到的桶为起始指针的DenseMapIterator,没找到返回以end桶为起始指针的DenseMapIterator
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);
}
}
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}
通过2.1和2.2可以知道如果类和元类已实现,是通过attachCategories方法附加上的,没有实现时是通过addForClass添加上的
。
2.3 attachCategories方法源码
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
......
/*
在启动期间,只有少数几个类具有超过64个分类。
这将使用少量堆栈,并避免使用malloc。
必须以正确的顺序添加类别,这是从前到后的。
为此,我们从头到尾迭代cats_list,向后建立本地缓冲区,然后在块上调用attachLists。
attachLists在列表的前面,因此最终结果按预期顺序排列。
*/
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(); //生成rwe
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
//从类别中根据是否为元类取出方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//类别方法列表个数等于64
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount); //把方法列表追加到rwe的methods中
mcount = 0; //把方法列表数置为0
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
......
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
//attachLists方法会插在列表的前面
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
......
}
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;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
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
//分配新的空间,然后把原来的数据移到新的lists的addedCount位置,然后把addedLists也就是新加入的列表从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方法我们可以得知类别的方法会根据顺序插入到头部,所以后加进来的分类方法反而位于前面(并不是覆盖),方法查找也是从前后往前查的,这里也可再次帮助理解findMethodInSortedMethodList方法中为什么probe–才能找到正确的分类方法。
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
......
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
......
}
return nil;
}
总结
- 分类本质上为category_t的结构体类型,它通过cls属性与主类关联。分类中实现的属性只是声明,不会自动生成getter/setter方法,需要自己手动通过关联属性来实现。
- 分类有两种加载方式,对于桩类、未实现的类,通过addForClass方法加入类中,对于已实现的类通过attachCategories方法追加入类。
- 分类通过以它附属的类为key,然后以{cat, hi}为值存放在桶中。通过LookupBucketFor查找,InsertIntoBucket负责插入。
- 在启动期间,只有少数几个类具有超过64个分类,所以把分类方法列表、属性列表、协议列表初始大小设为64,以方法列表为例,每当大小等于64时,就把方法列表先追加进rwe->methods中。不够64的最后在循环体外再追加一次,提升效率。
- 后追加的分类的方法、属性、协议会被加入到前面。
推荐阅读