IOS-Category实现原理

2019-08-25  本文已影响0人  就叫K

分类(Categroy)

你用分类都做了那些事?

特点

分类中都可以添加哪些内容

实现原理

调用加载栈

调用加载栈

实现原理

category源码分析(objc-756.2)

分类结构体
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);
};

remethodizeClass
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    // 获取为拼接的分类列表
    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);
    }
}
attachCategories
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    /*方法列表二维数组
        [[method_t,method_t,...],[method_t],[method_t,method_t,method_t],...]
    */
    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;
        }
    }
        //获取宿主类当中的rw数据,其中包含数组类的方法列表信息
    auto rw = cls->data();
        // 主要针对 分类中有关内存管理的相关方法,做一些特殊处理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
  
    /*
        rw代表宿主类
        methods代表宿主类的方法列表
        attachLists方法是 将含有mcount个元素的二维数组拼接到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);
}
attachLists
/*
    addedLists 上面传递过来的二维数组
    [[method_t,method_t,...], [method_t], [method_t,method_t,method_t],...]
     -----------------------  ----------  ---------------------------
        分类A的方法列表                           B                                C
     addedCount 二维数组长度 3
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // 类表中原有的元素总数
            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]));
        }
    }

分类添属性(成员变量)(关联对象)

@interface UIView (KTestCategroy)
@property (nonatomic,copy) NSString *name;
@end
@implementation UIView (KTestCategroy)
-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString*)name{
    return objc_getAssociatedObject(self, "name");
}
@end
{
  "0x847547475":{
    "@selector(name)":{
      "value":"KsView",
      "policy":"copy"
    }
  }
}
关联对象实现
objc_setAssociatedObject源码分析
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;
    
    assert(object);
    
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        //关联对象管理类
        AssociationsManager manager;
        // 获取其维护的一个Hashmap
        // 一个全局的容器
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // 根据对象指针查找对应的一个AssociationsHashMap结构的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                //如果找到这个Key,替换新值
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    //设置新值
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // 第一次为这个对象添加关联对象
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
          
            // 传过来的新值是nil,则清空原有的值
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

总结

  1. category为什么不能添加属性?

    category可以添加属性,但是不会生成实例变量和set/get方法,因为category_t结构体中不存在成员变量,成员变量是存放在实例对象中的,在编译好的那一刻就决定好了。而分类是在运行时才去加载的,那么就无法在程序运行时将分类的成员变量添加到实例对象的结构体重,所以说分类中不可以添加成员变量。实例变量没有set/get方法,也没有自己的isa指针,所以,添加属性时系统没有报错,但是也不能用。

  2. category中有load方法吗?load方法调用时机以及load方法能继承吗?

    category中有load方法;

    category中的load方法在程序装载类信息的时候就会调用;

    load方法可以继承,会先调用父类的load方法

    调用顺序:先调用类的+load方法,再调用分类的+load,先编译,先调用的原则,调用子类的+load方法前会先调用父类的+load

  3. load、initialize的区别,以及它们在category重写的时候的调用顺序

    区别就在于调用方式和调用时刻;

    调用方式:load是根据函数地址直接调用;initialize是通过objc_msgSend调用

    调用时刻:load是在runtime加载类、分类的时候调用(只调用一次);initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)

  1. 如何清空一个关联对象?

    传nil则清空

上一篇下一篇

猜你喜欢

热点阅读