美文网首页
OC中的Category

OC中的Category

作者: it小小菜鸟 | 来源:发表于2020-07-15 17:14 被阅读0次

注意:所有的分类方法都会被合并到class对象和meta-class对象中,不会覆盖掉原对象的方法。可以通过runtime去查看所有的方法,并选择调用。而系统objc_msgSend()函数,会通过isa指针,找到class对象,再找到方法列表,一旦找到方法实现,就会调用,就不会再查找下去了。

Category的实现原理?

  1. Category编译之后的底层结构是 struct _category_t 结构体,里面存储着分类的对象方法、类方法、属性、协议信息
  2. 在程序运行的时候,runtime会将Category的数据,合并到类信息中(class对象、meta-class对象)
解析过程:

1. 所有分类都是 struct _category_t 结构体:

struct _category_t {
    const char *name; // 分类名称
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; // 对象方法列表
    const struct _method_list_t *class_methods; // 类方法列表
    const struct _protocol_list_t *protocols; // 协议列表
    const struct _prop_list_t *properties; // 属性列表
};

2. 主要通过runtime在运行时,把分类结构体里面的内容加载到类的class对象和meta-class对象中

注意:原来的class对象方法会被移动到分类方法列表后面,所以会先调用分类中的方法。当多个分类都有相同方法时,就要看编译的顺序,最后编译的分类方法,会被添加到class对象的最前面,会最先调用。
编译顺序:在Xcode的Compile Sourcess中从上到下的顺序

3. 如图所示:

category.png

类和分类普通方法的调用顺序

  1. 会先调用分类中的方法,再调用类中的方法。通过attachCategories方法可以知道,attachCategories会先通过attachLists函数中的memmove把原对象的方法列表数据后移,再通过memcpy把分类中的方法列表拷贝进类方法列表中,这样分类中的方法列表就在前面了,所以会先调用
  2. 多个分类的调用顺序,跟编译顺序有关,后编译的分类中的方法,先调用。通过源码中的map_images_nolock可以知道。该方法中,会从最大值开始把分类信息递减添加到数组中,所以最后编译的分类会最先添加到数组中

Category和Extension的区别是什么?

  • extension 的内容在编译时就合并到类信息中(class对象、meta-class对象)
  • category 在编译时,先生成分类结构体对象,在运行时通过runtime添加到类信息中(class对象、meta-class对象)

源码分析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;
    }
};

objc4-781版源码分析加载过程

从objc-os.mm 文件 开始

  1. void _objc_init(void) :入口方法
  2. map_images():然后调用该方法
  3. map_images_nolock()再调用该方法: 读取模块信息,其中通过循环从 uint32_t i = mhCount 最大值开始循环添加所有分类信息到数组中,所以最后编译的添加在数组的最前面。这样最后编译的分类的对象方法,就会先调用,后面的相同方法就不会调用了
  4. _read_images():然后读取镜像(模块),所有的类都会被读取,该方法中会循环调用load_categories_nolock方法
  5. load_categories_nolock():该方法会读取所有分类的信息,然后通过attachCategories把所有的分类信息添加class对象和meta-class对象中
  6. attachCategories(cls, &lc,): 添加分类信息到类中,其中会调用attachLists方法添加分类信息,attachLists方法中会调用memmove和memcpy两个方法做添加操作。
  7. attachLists(): 会先调用memmove()把原来的数据后移,这样可以防止在执行memcpy时覆盖掉原数据。 然后再执行memcpy()把分类信息copy进来。这样把原来的数据后移,然后再把新的数据copy进来,就会使得class对象的方法,在分类对象方法的后面。

相关源码如下:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        // 倒序添加信息
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }
attachCategories
// 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;
    // cats_list 分类列表
    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);
// rwe 取出类的可读写数据表,用于添加分类的数据
    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);
                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);
        // 把方法数组添加到类的rwe数据表
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }
    // 把属性数组添加到类的rwe数据表
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
     // 把协议数组添加到类的rwe数据表
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
attachLists
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]));
        }
    }

相关文章

  • 结合 category 工作原理分析 OC2.0 中的 runt

    结合 category 工作原理分析 OC2.0 中的 runtime 结合 category 工作原理分析 OC...

  • 分类、类扩展与继承

    在OC中,扩展一个类的方式有两种,继承和分类。 分类(Category) 概念 分类(Category),是OC中...

  • OC 中的 category

    在谈category的时候,就需要对比的看下扩展。 扩展:编译的时候决定,一般来说 是声明 私有的属性和变量类别:...

  • OC中的Category

    注意:所有的分类方法都会被合并到class对象和meta-class对象中,不会覆盖掉原对象的方法。可以通过run...

  • OC中的Category(三)

    OC中的Category(三) OC中+initialize函数加载和调用 OC对象是在查找方法时判断自己有没有初...

  • swift中Extension的简单理解

    swift中的extension和OC中的Category有点类似,同时也比category的功能更加的强大,想e...

  • iOS面试技术点总结

    1.category和extension。 (分类(Category)是OC中的特有语法,它是表示一个指向分类的结...

  • OC中Category分析

    分类的底层结构 实现原理 将方法、属性、协议数据保存在category_t的结构体中,然后将结构体中的方法列表拷贝...

  • OC--Category、AssociatedObject

    Category原理、作用深入理解Objective-C:Category (OC1.0)结合 category ...

  • iOS 分类(category)和扩展(extension)

    分类(Category): 概念 分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。...

网友评论

      本文标题:OC中的Category

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