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

类扩展和关联对象

作者: 会跑的鱼_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