类扩展和关联对象

分类和类扩展

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表结构。

关联对象数据结构
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容