分类和扩展(Category)

作用:

当为类添加新方法时,OC一共提供了4中方式:

  1. 直接在类中再写一个方法,缺点:破坏了类的封装性。
  2. 通过继承再子类里扩展一个方法,缺点:开销大、增加代码复杂度
  3. 通过协议实现扩展一个类的方法,优点:该方式能够大大降低代码的耦合度,但是实现上会更复杂。
  4. 最后一种就是使用Category。

优点和缺点:

Category优点:

  • 在不改变一个类的情况下,对一个已存在的类添加新的方法
  • 可以在没有源代码的情况下对框架内的类进行扩展,例如NSString。
  • 减小个文件的体积
  • 可以按需加载不同的Category

Category缺点:

  • 方法的扩展是硬编码,不能动态添加
  • Category中方法的优先级高于原类中的方法,所以Category中的方法可能会覆盖原有类中相同名字的方法,从而造成未知问题。
  • 不能添加成员变量(并非不能,只不过比较复杂)
  • 可以添加属性,但是不会自动生成getter和setter方法,需要通过关联对象实现。
  • 同一个类的Category中不能有重复名字的方法。

Category实现的原理:

Category源码在:objc-runtime-new.h和objc-runtime-new.mm这两个文件中

Category runtime中的定义:

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

通过:

 struct method_list_t *instanceMethods;
 struct method_list_t *classMethods;
 struct protocol_list_t *protocols;
 struct property_list_t *instanceProperties;
复制代码

结构体中这四个变量可以看出,分类可以添加实例方法、类方法、协议和属性列表,但是唯独不能添加实例变量,因为没有存储实例变量对应的指针变量。

Category加载的详细步骤和调用到的函数:

_objc_init(void)->map_images(...)->map_images_nolock(...)->_read_images(...)->remethodizeClass(Class cls)->attachCategories(Class cls, category_list *cats, bool flush_caches)->void attachLists(List* const * addedLists, uint32_t addedCount
复制代码

函数介绍:

  • _objc_init:是runtime的入口函数,进行一些初始化操作
  • map_images:加锁
  • map_images_nolock:完成所有类的注册和fixup等工作,还包括一些初始化工作以及调用load类方法。
  • _read_images:完成类的加载、协议的加载、类别的加载等工作
  • remethodizeClass:将类别绑定到目标类上
  • attachCategories:将类别中的方法和属性绑定到目标类上。
  • attachLists:将目标类中的方法和分类中的方法放到一个列表中。

主要涉及Category原理的三个方法:_read_images、attachCategories、attachLists 来看看_read_images函数源码重要的部分:

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");

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

    // +load handled by prepare_load_methods()

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // Print preoptimization statistics
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;

        for (EACH_HEADER) {
            if (hi->isPreoptimized()) {
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            }
            else if (hi->info()->optimizedByDyld()) {
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            }

            classref_t *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;

                PreoptTotalClasses++;
                if (hi->isPreoptimized()) {
                    PreoptOptimizedClasses++;
                }
                
                const method_list_t *mlist;
                if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
                if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
            }
        }

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses, 
                     PreoptTotalClasses 
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    }
复制代码

其中在代码中:

 bool classExists = NO;
 if (cat->instanceMethods ||  cat->protocols  
    ||  cat->instanceProperties) 
 {
     // 绑定分类和目标类 
     addUnattachedCategoryForClass(cat, cls, hi);
     if (cls->isRealized()) {
         // 重新构建方法列表
         remethodizeClass(cls);
         classExists = YES;
     }
     if (PrintConnecting) {
         _objc_inform("CLASS: found category -%s(%s) %s", 
                     cls->nameForLogging(), cat->name, 
                     classExists ? "on existing class" : "");
     }
 }
复制代码

在有注释的地方,就是重要关键点:1、去绑定分类和目标类。2、重新构建方法列表。 接下来我们来看attachCategories函数:

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();

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

从源码可以看出,这里是将目标类中所有扩展出来的类别中的属性、方法、协议列表头取出放到一个列表里面,然后统一交给attachLists这个函数去处理。 接下来在看看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]));
    }
}
复制代码

这里强调一下,oldCount和newCount分别代表的是目标类中方法、属性、协议列表的长度还有类别中的方法、属性、协议列表的长度。通过realloc函数申请内存,这里使用realloc申请内存有几种情况,大家可以自行查阅,这里不再描述,当realloc重新申请好内存后,使用memmove和memcpy这两个函数,将列表内容放到我们重新申请的内存中,从这里可以得到,Category中的方法并没有覆盖目标类中的方法,只不过将Category中的方法放到了目标类方法的前面而已,在调用方法的时候,若Category中和目标类中有同名的方法,系统会先找到放到前面的类别中的方法,这就是为什么Category中方法的优先级高于目标类中的方法。

Category添加属性:

尽管在源码中我们看到了指向属性列表的指针变量,但是系统并没有给这里的属性生成getter和setter函数,需要手段生成实例变量,并为其生成getter和setter方法。

转载于:https://juejin.im/post/5d49ad0b6fb9a06b0e547a72

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值