美文网首页
分类及其底层实现

分类及其底层实现

作者: 高思阳 | 来源:发表于2019-03-03 23:58 被阅读0次

相关代码为objc_runtime-680版本

分类的作用

  • 给现有的类添加方法;

  • 将一个类的实现拆分成多个独立的源文件;

好处:
a)可以减少单个文件的体积
b)可以把不同的功能组织到不同的category里
c)可以由多个开发者共同完成一个类
d)可以按需加载想要的category 等等

  • 声明私有的方法。

分类的特点:

  • 运行时决议(编译的时候,分类中的内容还没有添加到宿主类中,而是在运行时,通过runtime把分类中的方法、属性、协议等添加到宿主类中)
  • 可以为系统类添加分类 (比如常用的布局分类自定义frame、center等)

分类可以添加以下内容:

  • 类方法和实例方法
  • 协议
  • 属性

分类数据结构的定义

通过下面分类的数据结构,可以知道为什么分类不可以添加实例变量,而可以添加类方法和实例方法以及协议和属性。在category中可以有属性(property),但是该属性只是生成了getter和setter方法的声明,并没有产生对应的实现,更不会添加对应的实例变量。如果想为实例对象添加实例变量,可以尝试使用关联引用技术。

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;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) return nil; // classProperties;
        else return instanceProperties;
    }
};

分类内容与宿主类合并的相关源码

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();
    
    /*
      只分析分类当中实例方法添加逻辑
      因此这里假设 isMeta = NO
    */
    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    //获取cls中未完成整合的所有分类
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        //将分类cats拼接到类cls上
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

//maptable.h
/***********************************************************************
* unattachedCategoriesForClass
* Returns the list of unattached categories for a class, and 
* deletes them from the list. 
* The result must be freed by the caller. 
* Locking: runtimeLock must be held by the caller.
* 获取cls中未完成整合的所有分类哈希表

**********************************************************************/
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
    runtimeLock.assertWriting();
    return (category_list *)NXMapRemove(unattachedCategories(), cls);
}

static NXMapTable *unattachedCategories(void)
{
    runtimeLock.assertWriting();
    //全局对象
    static NXMapTable *category_map = nil;
    if (category_map) return category_map;
    // fixme initial map size
    category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
    return category_map;
}

//相似的结构,可以在runtime相关的数据结构sideTable等里面看到
//可以看出就是一个hash表
typedef struct _NXMapTable {
    /* private data structure; may change */
    const struct _NXMapTablePrototype   *prototype;    //包含三个结构体指针,用于表中的数据处理和比较
    unsigned    count;                                                    //记录表中数据数量
    unsigned    nbBucketsMinusOne;                            //数组长度减一
    void    *buckets;                                                       //具体的表数据数组
} NXMapTable OBJC_MAP_AVAILABILITY;

typedef struct _NXMapTablePrototype {
    unsigned    (*hash)(NXMapTable *, const void *key);
    int     (*isEqual)(NXMapTable *, const void *key1, const void *key2);
    void    (*free)(NXMapTable *, void *key, void *value);
    int     style; /* reserved for future expansion; currently 0 */
} NXMapTablePrototype OBJC_MAP_AVAILABILITY;
NXMapTable具体结构
//将分类cats的方法列表、属性列表、协议列表拼接到类cls上
//假定cats中的分类已全部加载并且是按照编译顺序排序
//最后编译的分类排在最先
// 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, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    /*
      只分析分类当中实例方法添加逻辑
      因此这里假设 isMeta = NO
    */
    bool isMeta = cls->isMetaClass();

    /*mlists 是二维数组
      method_list_t **相当于一维数组里面保存着method_list_t *类型的数据
      而method_list_t *相当于一维数组里面保存着method_list_t类型的数据
      因此,是如下结构:
      [[method_t , method_t , ...], [method_t], [method_t , method_t ], ...] 
    */
    // 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);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    // 获取宿主类当中的rw数据,其中包含宿主类的方法列表信息(https://draveness.me/method-struct)
    auto rw = cls->data();
    // 主要是对分类中有关于内存管理相关方法的情况做特殊处理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    /*
        rw代表类
        methods代表类的方法列表
        attachLIsts方法:将含有mcount个元素的mlists拼接到rw的methods上
    */
    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 , method_t ], ...] 
  -----------------------------------   -------------    ----------------------------
    分类A中的方法列表(A)         B                          C

    addedCount = 3
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // 列表中原有元素总数 oldCount = 2
            // 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]));
            /* 内存拷贝
                [
                    A ---->  [addedLists中的第一个元素 ] 
                    B ---->  [addedLists中的第二个元素 ] 
                    C ---->  [addedLists中的第三个元素 ] 
                    [原有的第一个元素]
                    [原有的第二个元素] ]
                ]
                这就是分类方法会“覆盖”宿主类的方法的原因,注意这里
                覆盖其实不是真的覆盖,而是因为系统寻找方法是顺序遍
                历,而宿主类的方法在后面,所以分类有相同的方法会“覆
                盖”宿主类方法的实现
            */
            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]));
        }
    }

总结:

  • 分类添加的方法可以“覆盖”袁磊方法
  • 同名分类方法生效取决于编译顺序,最后编译的分类方法会生效
  • 名字相同的分类会引起编译报错

相关文章

  • 分类及其底层实现

    相关代码为objc_runtime-680版本 分类的作用 给现有的类添加方法; 将一个类的实现拆分成多个独立的源...

  • RACSubject及其底层实现

    上一篇文章 我们了解了RACSignal及其底层实现,这篇文章我们来了解一下RACSubject及其底层实现。 开...

  • iOS分类的实现原理简记

    该文为分类原理的简单记录,总结自如下文章,感谢作者分享: iOS底层原理总结 iOS分类底层实现原理小记 1、分类...

  • 100篇技术文章

    1.Class类的底层实现原理 - 链接 链接 2.Category分类的底层实现原理 - 链接 3.Block块...

  • Category 分类底层实现

    一:概念分析 1.分类和类公用一个类对象结构; 2.分类的类方法存储到元类里面; 3.分类的方法、协议、属性...

  • 机器学习5(轻量TensorFlow)教程

    2. 图像分类器 底层技术依靠TensorFlow实现,此图像分类器利用了Mobilenet分类模型 2.1. 用...

  • iOS基础知识(四)分类(category)和扩展(extens

    首先,分别来说下分类(category)和扩展(extension)的用处。然后来解读一下分类的底层实现 分类常见...

  • synchronized底层实现及其优化

    synchronized使用场景 synchronized关键字主要解决线程之同步互斥的问题,在JavaSE中Ha...

  • Runtime:OC分类的本质和底层实现

    一、分类是什么、我们一般用它来做什么二、分类的本质三、分类的底层实现四、分类的+load方法和+initializ...

  • 分类和类扩展

    1、分类实现原理 Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法...

网友评论

      本文标题:分类及其底层实现

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