OC分类一(Category)

2019-01-29  本文已影响25人  iLeooooo

I、题:Category的使用场景是什么?

答:对于很复杂的类,可以使用Category把这个类拆分成很多个模块。Appdelegate这个类使用Category的地方比较多,有很多初始化的东西都可以放在分类里面实现,比如第三方配置(Umeng,极光推送,网络配置)。


结论:

  1. 目标类的分类中的实例方法、类方法都是存储在该目标类的类对象和元类对象中,并不会重新创建分类的类对象和元类对象。程序运行过程中会把分类中的实例方法全部合并到类对象中,把类方法全部合并到元类对象中;注意不是在程序编译的时候合并过去的,而是在程序运行的时候,通过Runtime动态将分类的方法合并到类对象和元类对象中。
  2. 目标类:即LQPerson+Test,LQPerson+Eat中的LQPerson。
  3. 当程序编译完的时候,创建的Category分类都会变成category_t这种结构体数据,方法,属性,协议等数据都会存储在该结构体里面。每一个分类都会生成一个category_t结构体。
Category底层实现:
  1. 创建一个LQPerson的分类LQPerson+Test;
  2. 使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LQPerson+Test.m编译得到cpp文件查看源码;
  3. 编译后的源码
     struct _category_t {
         const char *name;    // 类名,如LQPerson
         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;    //  属性信息
     };
    
  4. 最新objc4-750的Runtime源码中对category_t的定义:
    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);
    };
    
  5. 在程序运行过程中,会利用运行时机制将每一个分类中的实例(类)方法合并到类(元类)对象中。
Category的加载处理过程:
  1. 通过Runtime加载某个类的所有Category数据;
  2. 把所有Category的方法、属性、协议数据合并到一个大数组中;(后参与编译的Category数据,会在数组的前面)
  3. 将合并的分类数据(方法、属性、协议),插入到目标类原来的数据前面。
第三步的核心操作:
// cls = [LQPerson class];
// cats = [category_t(Test), category_t(Eat)];
// 下面注释说明使用的是类对象,元类对象同理。
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_t, method_t],   // 一个分类的方法列表
         [method_t, method_t]    // 另一个分类的方法列表
    ]
    */
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    // 属性数组
    /*
     [
         [property_t, property_t],   // 一个分类的属性列表
         [property_t, property_t]    // 另一个分类的属性列表
    ]
    */
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    // 协议数组
    /*
     [
         [protocol_t, protocol_t],   // 一个分类的协议列表
         [protocol_t, protocol_t]    // 另一个分类的协议列表
    ]
    */
    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--) {
        // 取出某个分类,entry.cat就是`category_t`类型数据
        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);
}

/*addedLists:
   [
       [method_t, method_t],   // 一个分类的方法列表
       [method_t, method_t]    // 另一个分类的方法列表
    ]
  addedCount:2
*/
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]));
    }
}

源码解读顺序:
  1. objc-os.mm
    • _objc_init_
    • map_images
    • map_images_nolock
  2. objc-runtime-new.mm
    • _read_images
    • remethodizeClass
    • attachCategories
    • attachLists
    • reallocmemmovememcpy

注意:images不是图片的意思,是镜像、模块的意思。


J、题:Category的实现原理是什么?

答:Category编译之后的底层结构是struct category_t,里面存储着Category的对象方法、类方法、属性、协议信息;在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象)


K、题:分类(Category)和类扩展(Class Extension)有什么区别?

答:类扩展在编译的时候他的数据就已经包含在类信息中,而分类里面的内容是在程序编译的时候生成category_t结构体数据,在运行的时候利用运行时机制合并到类信息里面去的。


memmove、memcpy区别

memcpy在移动数据的时候容易出现错误
memmove在移动数据的时候可以保证数据的完整

L、题:把3412中的34移动到41位置上面,分别用上面2个命令会出现什么问题?

答:使用memcpy:会分别把3和4copy到4和1的位置,但是会有先后顺序,会先把3copy到4的位置,然后再把4copy到1的位置(步骤A),但是当把3copy到4的位置的时候,4的位置的值就不是4了,就变成3了,再来执行步骤A的时候就会把3copy到1的位置了,所以结果就变成了3332了。
使用memmove:会先判断是向前移动还是向后移动,如果是向后移动,就会先将4移动到1的位置,原来的位置还是4,此时是3442,然后再将3移动到4的位置,结果就变成了3342。

上一篇下一篇

猜你喜欢

热点阅读