欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

重学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的最后在循环体外再追加一次,提升效率。
  • 后追加的分类的方法、属性、协议会被加入到前面。
相关标签: # 重学OC系列