最新 Category 加载

1. Category的底层结构

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;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
复制代码

category_t是分类的底层结构,从结构中不难发现分类存储了自定义的类对象的方法属性、元类对象的方法属性以及协议。在分类中添加Property,不会自动生成get、set方法,但会把property添加到类中。swift

2. Category的加载

_objc_init>>map_images>>map_images_nolock>>_read_images数组

// Discover categories.
    for (EACH_HEADER) {
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        auto processCatlist = [&](category_t * const *catlist) {
            for (i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
                locstamped_category_t lc{cat, hi};
                
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Ignore the category.
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class",
                                     cat->name, cat);
                    }
                    continue;
                }
                
                // Process this category.
                if (cls->isStubClass()) {
                    // Stub classes are never realized. Stub classes
                    // don't know their metaclass until they're
                    // initialized, so we have to add categories with
                    // class methods or properties to the stub itself.
                    // methodizeClass() will find them and add them to
                    // the metaclass as appropriate.
                    if (cat->instanceMethods ||
                        cat->protocols ||
                        cat->instanceProperties ||
                        cat->classMethods ||
                        cat->protocols ||
                        (hasClassProperties && cat->_classProperties))
                    {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                } else {
                    // First, register the category with its target class.
                    // Then, rebuild the class's method lists (etc) if
                    // the class is realized.
                    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(_getObjc2CategoryList(hi, &count));
        processCatlist(_getObjc2CategoryList2(hi, &count));
    }
复制代码

在这个方法中会去找到全部的分类,封装成locstamped_category_t结构体对象,调用objc::unattachedCategories.addForClass(lc, cls);保存起来,后面会根据cls取出来全部的分类,真正加载分类到对应的类中是在下面的地方markdown

/ Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &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);
        }
    }
复制代码

在这个地方会调用addClassTableEntry()保存全部的类和元类,在realizeClassWithoutSwift()methodizeClass()中会把分类的内容加载到对应的类中,看下面:app

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // 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));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->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);

}
复制代码

这个objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS)会真正加载该类的全部分类内容:oop

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();
        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);
        }
    }
复制代码

attachToClass()方法内会根据previously(传进来的就是该类cls)去取出以前objc::unattachedCategories.addForClass(lc, cls);保存起来的全部分类,而后调用attachCategories()来把分类的内容加入到类中ui

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 rw = cls->data();

    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);
                rw->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) {
                rw->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) {
                rw->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rw->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rw->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
复制代码

获取到分类的方法、属性、协议而后经过attachLists()添加类中,this

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
            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]));
        }
    }
复制代码

该方法不难看出,后加入尽可能的方法越在前面,并且类的方法、属性、协议等是个二维数组。spa

2. Category +load方法

_objc_init>>load_imagesprepare_load_methods()会把class存储到loadable_classes中,把分类存储到loadable_categories中,call_load_methods()方法中会取出来类和分类而后取出+load方法地址,地址调用(*load_method)(cls, @selector(load))ssr

下方是一些关键部分代码:code

void prepare_load_methods(const headerType *mhdr)
{
    ···
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));	//会把class存储到`loadable_classes`
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        ····
        add_category_to_loadable_list(cat); //分类存储到`loadable_categories`中
    }
}
复制代码
void call_load_methods(void)
{
    ···

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads(); 
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);
    
	···
}
复制代码

三省:

  1. Category的使用场合是什么?
  • 这个相信你们都有理解,再也不赘述
  1. Category的实现原理
  • Category编译以后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息;而后在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
  1. Category和Class Extension的区别是什么?
  • Class Extension在编译的时候,它的数据就已经包含在类信息中
  • Category是在运行时,才会将数据合并到类信息中
  1. Category中有load方法吗?load方法是何时调用的?load 方法能继承吗?
  • 有load方法
  • load方法在runtime加载类、分类的时候调用,上述已经说明介绍
  • load方法能够继承,可是通常状况下不会主动去调用load方法,都是让系统自动调用
  1. load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
  • load、initialize方法的区别什么
  • load是runtime加载类、分类的时候调用(只会调用1次)
  • initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用屡次)
  • load 调用的顺序
  • 先调用类的load
  • 先编译的类,优先调用load
  • 先调用父类的load,再调用子类的load
  • 出现继承时他们之间的调用过程?
  • 调用子类的load以前,会先调用父类的load
  • initialize调用的顺序
  • 先初始化父类
  • 再初始化子类(可能最终调用的是父类的initialize方法)
  1. Category可否添加成员变量?若是能够,如何给Category添加成员变量?
  • 不能直接给Category添加成员变量,可是能够间接实现Category有成员变量的效果
相关文章
相关标签/搜索