runtime05 - catogory

164 阅读5分钟

runtime源码版本: objc4-818.2(opensource.apple.com/tarballs/ob…)

category作用:

  1. 为已有类扩展功能
  2. 拆分臃肿类

category的结构

struct category_t {
    const char *name; // 分类名
    classref_t cls;   // 对应的主类
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; // 实例方法
    WrappedPtr<method_list_t, PtrauthStrip> 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原理:

在编译期, 每个分类都转换成一个category_t结构体, 在运行期,利用runtime的机制将分类中的方法 / 协议 / 属性附加到类对象和元类对象的方法 / 协议 / 属性列表中

与主类有同名方法时, 或者与其他分类同名时, 调用顺序是怎么样的

  1. 与主类有同名方法时,优先调用分类的方法
  2. 与其他分类有同名方法时, 越晚编译的越优先调用, 因为越晚编译的分类的方法会附加在方法列表中的越前边

与extension的区别

  1. extension在编译器决议,在编译的时候,将扩展中的内容与已有类组合成一个完整的类, category在运行期决议,在运行时,将自己的方法 / 协议 / 属性附加到类对象和元类对象的方法 / 协议 / 属性列表中
  2. 由于1的缘故,并且系统类都在动态库,不参与我们的编译,所以我们无法给系统类增加扩展,只能给系统类增加分类

分类是否能增加成员变量, 为什么

不能

一方面, 分类结构体中没有ivarList

另一方面, 类结构中, class_ro_t 中包含instanceSize, 也就是说, 在编译时, 每个对象的尺寸大小已经固定下来了, 不能再通过category或者runtime来动态添加成员变量了, 一旦可以动态增加成员变量, 那每个已经创建的对象的内存布局都会发生变化.这样太不安全

如何给 Category 添加属性?关联对象以什么形式进行存储?

category本身支持添加属性, 只是不会生成待下划线的成员变量以及对应的set/get方法

所以, 需要我们手动实现set/get方法, 利用关联来实现目的

关联对象本质是一个双重的HashMap

分类方法具体是怎么附加到类的

  1. 新建一个列表mlist,根据编译结果mach-o,取出分类,从后往前,将分类的方法列表依次加入到mlist中
  2. 将主类的方法列表容器class_rw_t->methods扩容, 将主类原有的方法列表, 往后挪动mlist长度的位置
  3. 然后将这个列表中的内容, 从前往后依次附加到主类的class_rw_t->methods中
  4. 苹果有个小优化,每次mlist长度最大为64,也就说每批处理64个,分批处理

具体代码实现:


// 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);
}

// 核心在这里
attachLists(mlists, mcount);
// 小技巧
1. cats_list的列表中的分类, 最前边的分类的方法会放到mlists的最后边.
2. 一次性最多attachLists 64个方法/协议/属性列表到类本身的方法/协议/属性列表
class list_array_tt {
    
    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;

            // 核心在这里
            // 将原有的列表往后挪addedCount个位置, 倒序的挪是为了避免覆盖有用值
            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();
        }
    }

}

源码路径

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])

void  map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)

static void load_categories_nolock(header_info *hi) 

static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)