美文网首页
类扩展和关联对象

类扩展和关联对象

作者: 会跑的鱼_09 | 来源:发表于2020-12-06 20:37 被阅读0次

    分类和类扩展

    OC类的加载中我们分析过分类的底层实现,其实是一个category_t的结构体,那现在来看一下类扩展的底层实现。

    //定义一个类以及其扩展
    @interface Teacher : NSObject
    @property (nonatomic, strong) NSString *normalName;
    - (void)normalSayHello;
    @end
    
    @interface Teacher ()
    @property (nonatomic,copy) NSString *extName;
    - (void)ext_sayhello;
    @end
    
    @implementation Teacher
    - (void)normalSayHello {
    }
    
    - (void)ext_sayhello {
    }
    @end
    
    //然后用clang编译器转换成c++代码看一下底层实现逻辑, clang -rewrite-objc main.m -o main1.cpp
    
    static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[2];
    } _OBJC_$_INSTANCE_VARIABLES_Teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        2,
        {{(unsigned long int *)&OBJC_IVAR_$_Teacher$_normalName, "_normalName", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_Teacher$_extName, "_extName", "@\"NSString\"", 3, 8}}
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[6];
    } _OBJC_$_INSTANCE_METHODS_Teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        6,
        {{(struct objc_selector *)"normalSayHello", "v16@0:8", (void *)_I_Teacher_normalSayHello},
        {(struct objc_selector *)"extSayhello", "v16@0:8", (void *)_I_Teacher_extSayhello},
        {(struct objc_selector *)"normalName", "@16@0:8", (void *)_I_Teacher_normalName},
        {(struct objc_selector *)"setNormalName:", "v24@0:8@16", (void *)_I_Teacher_setNormalName_},
        {(struct objc_selector *)"extName", "@16@0:8", (void *)_I_Teacher_extName},
        {(struct objc_selector *)"setExtName:", "v24@0:8@16", (void *)_I_Teacher_setExtName_}
    };
    
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[1];
    } _OBJC_$_PROP_LIST_Teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"normalName","T@\"NSString\",&,N,V_normalName"}}
    };
    

    可以看到extSayhello和普通的normalSayHello一起在类的methodlist中,extName和normalName都在实例变量ivarlist中,但extName不在proplist中,所以extName不能对外暴露,另外extName也生成了对应的get、set方法。
    再来看一下分类在底层的实现:

    //定义这样一个分类,然后还是clang转换成c++代码查看
    @interface Teacher (CategoryA)
    @property (nonatomic, copy) NSString *catName;
    - (void)categorySayhello;
    @end
    
    @implementation Teacher(CategoryA)
    
    - (void)categorySayhello {}
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_Teacher_$_CategoryA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"categorySayhello", "v16@0:8", (void *)_I_Teacher_CategoryA_categorySayhello}}
    };
    
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[1];
    } _OBJC_$_PROP_LIST_Teacher_$_CategoryA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"catName","T@\"NSString\",C,N"}}
    };
    
    static struct _category_t _OBJC_$_CATEGORY_Teacher_$_CategoryA __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Teacher",
        0, // &OBJC_CLASS_$_Teacher,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Teacher_$_CategoryA,
        0,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Teacher_$_CategoryA,
    };
    

    可以看到分类的信息统一是生成了一个category_t的对象,而extName虽然也在proplist中,但没有对应的get、set方法。分类的方法categorySayhello也是重新定义了一个结构体存储,和主类的方法分开的。

    关联对象的代码分析

    那在分类中的属性我们通常实现其get、set方法都会使用到runtime的api:objc_setAssociatedObject,先看一下它的代码实现:

    void
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    {
        //这个SetAssocHook是个静态变量,调用get方法就是拿初始化变量传进去的函数指针,所以SetAssocHook.get()就相当于_base_objc_setAssociatedObject
        SetAssocHook.get()(object, key, value, policy);
    }
    static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
    
    //那接下来来到这
    static void
    _base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    {
      _object_set_associative_reference(object, key, value, policy);
    }
    
    void
    _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
    {
        if (!object && !value) return;
        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));
        
        //把传进来的object对象包装成DisguisedPtr类型的
        DisguisedPtr<objc_object> disguised{(objc_object *)object};
        //把传进来的policy和value包装成ObjcAssociation类型的
        ObjcAssociation association{policy, value};
    
        //根据policy对传进来的value进行retain或者调用copy方法
        association.acquireValue();
        {
            //创建一个AssociationsManager对象
            AssociationsManager manager;
            
            //拿到全局的关联对象表AssociationsHashMap
            AssociationsHashMap &associations(manager.get());
    
            if (value) {
                //尝试从全局关联对象表中查找该对象的信息,disguised就是object这个对象的包装,
                auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
                //second表示取值时是否取到,如果未取到,会先插入一个默认值并返回出来,但在second中有标识是否真的找到,为true表示未找到,false表示找到了
                if (refs_result.second) {
                    //如果没拿到,则object是第一次放到关联对象中来,需要把对象标记一下,方便在对象释放时移除全局关联对象表中的数据
                    object->setHasAssociatedObjects();
                }
                //一个对象可能有多个关联属性,所以取出来所有的关联属性信息
                auto &refs = refs_result.first->second;
                //尝试根据本次传进来的key会所有的关联属性中去找,如果未找到会先插入一个默认值然后返回出来
                auto result = refs.try_emplace(key, std::move(association));
                if (!result.second) {
                    //如果找到了,用association中的value和policy替换旧的值
                    association.swap(result.first->second);
                }
            } else {
                //插入空值则直接清空全局的关联对象表即可
                auto refs_it = associations.find(disguised);
                if (refs_it != associations.end()) {
                    auto &refs = refs_it->second;
                    auto it = refs.find(key);
                    if (it != refs.end()) {
                        association.swap(it->second);
                        refs.erase(it);
                        if (refs.size() == 0) {
                            associations.erase(refs_it);
                        }
                    }
                }
            }
        }
        //如果前面retain过了,则此时release掉
        association.releaseHeldValue();
    }
    
    

    关联对象的数据结构

    通过分析我们可以发现AssociationsManager并不是全局唯一的,它内部使用的AssociationsHashMap才是全局的关联对象表,另外AssociationsHashMap中有会有多个对象信息ObjectAssociationMap,而一个对象可以有多个关联属性所以ObjectAssociationMap中又有多个ObjcAssociation信息。这样就构成了一张二维的hash表结构。

    关联对象数据结构

    相关文章

      网友评论

          本文标题:类扩展和关联对象

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