【iOS重学】关联对象的底层原理

写在前面

本文主要探究一下iOS中如何给分类添加属性以及关联对象的底层原理是什么,建议大家看本篇文章的时候参考objc4源码一起看会更好。

如何给分类添加属性

// Person + Test 类
@interface Person (Test)

@property (nonatomic, copy) NSString *name;

@end

@implementation Person (Test)

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @selector(name));
}

@end

解释
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)参数解释:
1、object:需要关联的对象
2、key:关联key
3、value:关联值
4、policy:关联策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 对应 assign       
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //  对应 strong,nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 对应 copy,nonatomic 
OBJC_ASSOCIATION_RETAIN = 01401, // 对应 strong,atomic                                     
OBJC_ASSOCIATION_COPY = 01403 // 对应 copy,atomic
};

设值原理

设值调用的是objc_setAssociatedObject,里面调用的是_objc_set_associative_reference,设值的核心方法就在_objc_set_associative_reference里面,如下:

1.png

2.png

_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));
    // 把关联对象object包装成一个DisguisedPtr类型的数据结构
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 把关联策略policy和具体关联的值value包装成一个ObjcAssociation的数据结构
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    // 根据不同的策略类型做相应的处理
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        // AssociationsManager 是关联对象管理类,里面有一个静态变量_mapStorage,要注意的是manager并不是唯一的。
        AssociationsManager manager;
        // 通过 manager.get()来获取所有的关联表associations 类型是 AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {
            // 根据关联的对象disguised去关联表associations中查找对应的ObjectAssociationMap类型的value,如果没有就创建一个插入到associations里面
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true; // 设置为true
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            auto result = refs.try_emplace(key, std::move(association)); // 根据传入的key找到对应的bucket,替换掉原来的或者插入新的association,并且设置关联策略。
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            // 如果value值为nil 通过传入的关联对象disguised找到相应的AssociationsHashMap
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key); // 通过传入的key去找到ObjectAssociation
                if (it != refs.end()) {
                    association.swap(it->second); // 进行擦除操作
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        // 如果有关联对象
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    // 对association进行一次release操作
    association.releaseHeldValue();
}

从上面我们可以看到设置关联的四个主要对象:

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation

其中,AssociationsManager的结构为:

class AssociationsManager {
  using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
  static Storage _mapStorage;
  
   AssociationsHashMap &get() {
      return _mapStorage.get();
   }

  static void init() {
      _mapStorage.init();
  }
};

AssociationsHashMap的结构为:

DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> 

ObjectAssociationMap的结构为:

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

ObjcAssociation的结构为:

class ObjcAssociation {
  uintptr_t _policy; // 关联策略
  id _value; // 关联值value
}

这四个对象之间的关系如下图所示:

3.png

对照objc_setAssociatedObject(id object, cons void *key, id value, objc_AssociationPolicy policy)方法:

  1. 通过AssociationsManagermanager.get()获得AssociationsHashMap
  2. AssociationsHashMap中的key是关联对象objectvalueObjectAssociationMap
  3. ObjectAssociationMap中的key是方法中的keyvalueObjectAssociation
  4. ObjectAssociation中存放的就是方法中的value和关联策略policy

通过上面的分析,设置关联对象的底层原理现在就很清晰了。

取值原理

取得调用的是objc_getAssociatedObject,里面调用的是_object_get_associative_reference,取值的核心方法就在_object_get_associative_reference里面,如下:

4.png

5.png

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 通过object可以获取ObjectAssociationMap
        if (i != associations.end()) {
            // 遍历AssociationsHashMap
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key); // 通过key可以获得ObjcAssociation
            if (j != refs.end()) {
                // 遍历ObjectAssociationMap
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue(); // 返回取到的值
}

总结

通过上面分析,我们需要知道:

  1. 关联对象并不是存储在被关联对象本身的内存里面,而是存储在一个全局的AssociationsHashMap里面。
  2. 设置关联对象为nil就相当于是移除关联对象。
  3. 移除所有的关联对象:objc_removeAssoociatedObjects
  4. 关联对象的策略里面没有 weak属性。
  5. 关联对象被移除的时候,相应的关联属性也会被移除。

写在最后

关于关联对象的底层原理我们就简单分析到这里,如有错误请多多指教,最后欢迎到我的个人技术博客逛逛。

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

推荐阅读更多精彩内容