美文网首页
【iOS重学】关联对象的底层原理

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

作者: 重庆妹子在霾都 | 来源:发表于2022-11-30 12:13 被阅读0次

写在前面

本文主要探究一下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. 关联对象被移除的时候,相应的关联属性也会被移除。

写在最后

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

相关文章

网友评论

      本文标题:【iOS重学】关联对象的底层原理

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