美文网首页
Category的原理

Category的原理

作者: 昵称是乱起的 | 来源:发表于2019-01-21 12:53 被阅读22次
分类做了哪些事情

把一些体积庞大的类按功能拆解开放在分类中
声明私有方法
把Framework的私有方法公开
分类添加的方法可以覆盖原来类中的方法,同名的分类方法谁能生效剩下取决于编译顺序,后编译的会覆盖前面的

看一下分类的底层
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);
};

发现分类的结构体里面没有成员变量的数组,这就是为什么分类不能添加成员变量的原因

看一下分类的调用栈,看看分类是怎么合并的
void _objc_init(void)  
└──const char *map_2_images(...)
    └──const char *map_images_nolock(...)
        └──void _read_images(header_info **hList, uint32_t hCount)
             └──static void remethodizeClass(Class cls)
                  └──static void attachCategories(Class cls, category_list *cats, bool flush_caches)
void _objc_init(void)
{
    //省略掉非关键代码
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[]) {
     //省略掉非关键代码
     _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
     remethodizeClass(cls);
}
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    isMeta = cls->isMetaClass();
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}
static void  attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    bool isMeta = cls->isMetaClass();
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));
    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
   auto rw = cls->data();
   repareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
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;
           //进行内存移动 把原来的数组里面的数据往后移动了addedCount个大小的位置
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
           //再把分类二维数组里面的方法列表copy到这个扩容的数组里面来
            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]));
        }
    }
总结一下attachCategories代码思路,以合并实例方法为主

创建一个二维数组 method_list_t **mlists = (method_list_t *) malloc(cats->count * sizeof(mlists))
获得所有分类的数量int i = cats->count;
倒叙遍历while (i--)(后来编译的会放在数组前面)
获得i所对应的的实例方法列表数组method_list_t *mlist = entry.cat->methodsForMeta(NO);
然后把method_list_t放在mlists二维数组里面,是直接把一维数组放在二维数组里面,也就是说不同分类之间的方法列表是分开的
下面的类方法、协议和属性列表的合并是一样的
根据(bits & FAST_DATA_MASK)获得class_rw_t
然后class_rw_t得到里面的methods二维数组,进行数组合并rw->methods.attachLists(mlists, mcount);

attachLists里面的思路

oldCount是class_rw_t里面methods的数量
newCount是oldCount加上分类的数量
重新分配数组的大小setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
再把分类二维数组里面的方法列表copy到这个扩容的数组里面来memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));

其实这只是个大体思路而已,具体还有很多细节,Runtime源码中自己去看

相关文章

网友评论

      本文标题:Category的原理

      本文链接:https://www.haomeiwen.com/subject/mdyfjqtx.html