iOS底层之关联对象

首先我们来简单的描述一下分类的一些基本概念:
1、用来给类添加新方法
2、不能给类添加成员属性,添加了成员变量,也无法取到
3、注意:其实可以通过runtime给分类添加属性
4、分类中用@property定义变量,只会生成变量的getter,setter方法声明,不能生成方法实现和带下划线的成员变量。

那么问题来了, 分类是不能直接添加属性的,那么要给分类添加属性,那就需添加关联对象或者重写,在.m文件添加setter方法的实现。

请看下面代码,在main.m中添加了对Person类的分类,在分类中有两个属性,且在main函数中创建Person对象并对分类的属性赋值,其中,分类的属性利用runtime来实现了gettersetter方法。

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person+LGEXT.h"

@interface Person (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end

@implementation Person (LG)
- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}

- (void)setCate_name:(NSString *)cate_name{
/**
 1: 对象
 2: 标识符
 3: value
 4: 策略
 */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        person.cate_name = @"OC";
        NSLog(@"%p",person);
    }
    return 0;
}

我们首先在setter方法出打上断点,来看看它是否会执行这个方法:

iShot2020-10-21 16.00.06.png

可以看到,会执行这个方法,而当我们点击方法进去之后,你会发现,他们的整个方法实现都在一块,首先SetAssocHook,然后_base_objc_setAssociatedObject,之后就会执行_object_set_associative_reference方法。

iShot2020-10-21 16.02.12.png

_object_set_associative_reference方法中有很多代码:

void
_object_set_associative_reference(id object, const 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;
    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));
    // 包装了一下 对象
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 包装一下 policy - value
    ObjcAssociation association{policy, value};
    // retain the new value (if any) outside the lock.
    association.acquireValue();
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 空的桶子
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                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);
                    }
                }
            }
        }
    }
    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

上面的代码很多,将部分代码收起来,可以看到一共就这么几行代码:


iShot2020-10-21 16.06.38.png

从下图可以看到,167行代码中的DisguisedPtr是对对象进行包装,

iShot2020-10-21 16.17.14.png

ObjcAssociation的作用同样是对policyvalue进行包装,其中value的值是上面代码对cate_name赋的值OC

iShot2020-10-21 16.18.48.png

看下图,acquireValue()的作用其实是对_valuepolicy的判断;

iShot2020-10-21 16.21.47.png

接下来就是172-201行的局部作用空间代码,如下图:


iShot2020-10-21 16.23.30.png
iShot2020-10-21 16.24.16.png

首先看到AssociationsManager,看上图,它的重要的地方其实是方框中的c++代码。至于它的作用,下面我用一个例子来表示:
main.m改成main.mm,在main.m中写一个结构体:

struct Objc {
    Objc()   { printf("来了");}
    ~Objc()  {  printf("走了"); }
};

然后在函数中调用:

iShot2020-10-21 16.27.50.png

看最终执行完毕的结果:


iShot2020-10-21 16.28.09.png

这个函数的作用其实就是在程序开始时执行一次,在函数结束完毕后,再执行一次。而程序中是对代码进行加锁和解锁;它可以有多个这样的AssociationsManager

AssociationsHashMap从名字看,像一个哈希表,里面有很多关联对象,里买呢的类型类似于key-value健值对的形式存在;继续执行内部代码块:
当执行到if (refs_result.second) {行时,我们在控制台来看一下一些属性的值:

(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
(lldb) p association
(objc::ObjcAssociation) $1 = {
  _policy = 3
  _value = 0x0000000100004038 "OC"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
  Buckets = 0x0000000000000000
  NumEntries = 0
  NumTombstones = 0
  NumBuckets = 0
}
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $4 = {
  first = {
    Ptr = 0x0000000101006810
    End = 0x0000000101006850
  }
  second = true
}
(lldb) 

看到refs_result的时候,可以看到$4的类型很长,我们不用去管它,去看看给它赋值的函数try_emplace
看下图,它其实是分两部分,一部分是false,一部分是true,从判断中可以看出来,当TheBucket为空时,就会执行InsertIntoBucket,并返回true,当TheBucket有值之后,就会返回false

iShot2020-10-21 16.42.19.png

下图是当value有值的时候,程序执行的代码块:

iShot2020-10-21 17.17.46.png

根据代码执行的流程分析的结果,程序第一次进入try_emplace走的是InsertIntoBucket,而在第二次执行try_emplace走的同样是InsertIntoBucket方法:
也就是说,第一次是从哈希表读取refs_result,而disguised是传入的值,接着判断refs_result.second是不是第一次来,当结果为真,调用setHasAssociatedObjects()函数。
接着引用refs_result.firstsecond值,对引用的refs进行第二次调用try_emplace,接着插入ObjcAssociation的值。

下面是分别进入两次后的TheBucket的值:

(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x000000010068b5b0
(lldb) p TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
(lldb) 

接下来看一下当value为空时,程序执行else部分:

iShot2020-10-21 16.50.53.png

上图部分代码的作用其实就是将关联对象进行消除;

总结: 其实就是两层哈希map , 存取的时候两层处理(类似二位数组)

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