美文网首页
OC分类一(Category)

OC分类一(Category)

作者: iLeooooo | 来源:发表于2019-01-29 17:55 被阅读25次

    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。

    相关文章

      网友评论

          本文标题:OC分类一(Category)

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