IOS底层底层之类扩展&关联对象

721 阅读15分钟

前言

IOS底层原理之类和分类加载 中探究了分类的加载。今天探究下分类中属性的存储,也就是大家常说的关联对象,以及类扩展的探究

准备工作

类扩展

类扩展作用

  • 为某个类附加额外的属性,成员变量,方法声明
  • 网上许多博客都写到类扩展是一种特殊的匿名分类,这种说法我是不认同,分类和类扩展是有本质的区别的
  • 分类的底层是category_t结构题类型,而类扩展底层是直接编译到主类中的
  • 一般的类扩展写到.m文件中,一般的私有属性或者想要独立区别的属性写到类扩展

类扩展格式

.m文件中的格式

@interface LWTeacher()
{
    NSInteger  height; //成员变量
}
@property(nonatomic,assign)NSInteger  lw_age;//属性
-(void)helloWord;//对象方法
+(void)helloClass;//类方法
@end

这种声明在.m文件的类扩展,基本上作为开发者每天都在用,但是我们确很少有人知道这就是类扩展 注意的是类扩展必须写在类的声明和类的实现直接

疑问:在.m文件中没看到类的声明,只有扩展和实现
类的声明是在.h文件中的,在编译时.h文件会被展开放在.m文件中

Extension

#import "LWTeacher.h"

@interface LWTeacher ()
@property(nonatomic,assign)int  age;
@end

创建类扩展时发现只有一个.h文件,没有与之对应的.m。类扩展的所有的实现都是在一个.m文件中实现的,其实Extension就是相当于把.m文件中类扩展的方式写到一个单独的头文件中,其实没啥区别

类扩展底层探究

LWTeacher写在main.m目的是为了更好的探究.cpp文件,代码如下

 @interface LWTeacher : NSObject
- (void)sayHello;
@end

@interface LWTeacher()
{
    NSInteger height;
}

@property(nonatomic,assign)NSInteger  lw_age;
-(void)helloWord;
+(void)helloClass;
@end

@implementation LWTeacher
+(void)load{
}
- (void)sayHello{
}
- (void)helloWord{   
}
+(void)helloClass{    
}
@end
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWTeacher * teacher = [[LWTeacher alloc] init];
        [teacher sayHello];
    }
    return 0;
}

main.m文件生成main.cpp文件

image.png

图中显示类扩展的变量和方法都是在编译时就已经确定,其实就是存储在类中。下面就验证下是否是在编译期就确定的,在_read_images方法中下断点

image.png

图中很明显类扩展的方法也是在ro中的,ro是在编译期就确定的。所以现在得出结论类可以有多个类扩展,但是所有的实现都是在.m文件中实现的

关联对象 - set

关联对象应用的场景一般是在分类中添加属性,现在就探究下关联对象底层实现。创建LWTeacher+HH分类和LWPerson+LWA分类,源码如下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * person = [[LWPerson alloc] init];
        person.lw_name = @"luwenhan";
        person.lw_showHello = @"hello";
        person.lw_showHello1 = @"H1111";
        person.lw_showHello2 = @"H2222";
        LWTeacher * teacher = [[LWTeacher alloc] init];
        teacher.lw_teacher = @"teacher";
    }
    return 0;
}
//LWPerson+LWA
@implementation LWPerson (LWA)
+(void)load{
    
}
-(void)setLw_name:(NSString *)lw_name{

    objc_setAssociatedObject(self, "lw_name", lw_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
}

-(NSString *)lw_name{
    return  objc_getAssociatedObject(self, "lw_name");
}

...
// LWTeacher+HH
@implementation LWTeacher (HH)
+(void)load{
    
}
-(void)setLw_teacher:(NSString *)lw_teacher{
    objc_setAssociatedObject(self, "lw_teacher", lw_teacher, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)lw_teacher{
    return  objc_getAssociatedObject(self, "lw_teacher");
}

@end

LWPerson+LWA分类定义4个属性,在LWTeacher+HH分类中创建定义一个属性。在objc_setAssociatedObject中添加断点,运行源码,进入objc_setAssociatedObject

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

进入_object_set_associative_reference方法里面的代码比较多,看下核心代码的实现

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
  
    if (!object && !value) return; //没有值直接返回
    if (object->getIsa()->forbidsAssociatedObjects())
        ...
    //将 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.
    // 根据policy策略去判断是进去retain还是copy操作
    association.acquireValue();

    bool isFirstAssociation = false;
    {   // 实例化 AssociationsManager 注意这里不是单例
        AssociationsManager manager;
         //实例化 全局的关联表 AssociationsHashMap 这里是单例
        AssociationsHashMap &associations(manager.get());
        // 如果value有值
        if (value) {
            //AssociationsHashMap:关联表 ObjectAssociationMap:对象关联表
            //首先根据对象封装的disguised去关联表中查找有没有对象关联表
            //如果有直接返回表,如果没有则根据`disguised`去创建对象关联表
            //创建ObjectAssociationMap时当(对象的个数+1大于等于3/4,进行两倍扩容
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                //表示第一次关联该对象
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            //获取ObjectAssociationMap中存储值的地址
            auto &refs = refs_result.first->second;
            //将需要存储的值存放在关联表中存储值的地址中
            //同时会根据key去查找,如果查找到`result` = false ,如果找不到就创建result = true
            //创建association类型当(association的个数+1)超过3/4,就会进行两倍扩容
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                //交换association和查询到的`association`进行交换
                //其实可以理解为更新查询到的`association`数据,新值替换旧值
                association.swap(result.first->second);
            }
        } else {
            //value没有值走else流程
            //查找disguised 对应的ObjectAssociationMap
            auto refs_it = associations.find(disguised);
            //如果找到对应的 ObjectAssociationMap 对象关联表
            if (refs_it != associations.end()) {
                //获取 refs_it->second 里面存放了association类型数据
                auto &refs = refs_it->second;
                //根据key查询对应的association
                auto it = refs.find(key);
                if (it != refs.end()) {
                    //如果找到更新旧的association里面的值
                    //旧的association会存放在association中
                    association.swap(it->second);
                    //value= nil时释放关联对象表中存的`association`
                    refs.erase(it);
                    //如果该对象关联表中所有的关联属性数据被清空,那么该对象关联表会被释放
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }
    //每一次第一个关联对象调用setHasAssociatedObjects
    //通过setHasAssociatedObjects方法`标记对象存在关联对象`设置`isa指针`的`has_assoc`属性为`true`
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    //释放旧值因为如果有旧值会被交换到`association`中
    //原来`association`的新值会存放到对象关联表中
    association.releaseHeldValue();
}

_object_set_associative_reference核心作用其实就两个

  • 根据object在关联表中查询ObjectAssociationMap如果没有就去开辟内存创建ObjectAssociationMap,创建的规则就是在3/4时,进行两倍扩容
  • 将根据key查询到相关的association就是关联的数据 valuepolicy,如果查询到直接更新里面的数据,如果没有则去获取空的asociation类型然后将值存放进去,如果ObjectAssociationMap中是第一次去关联数据或者关联的数据的个数满足了3/4时,进行两倍扩容
  • 扩容的规则和cache方法存储的规则时一样的

AssociationsManager manager可能大家有疑问为什么不是单例,而AssociationsHashMap &associations是单例,探究下AssociationsManagerAssociationsHashMap结构

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    // 构造函数 加锁
    AssociationsManager()   { AssociationsManagerLock.lock(); }    
    // 析构函数 解锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    // 获取全局的一张AssociationsHashMap表
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

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

源码显示static Storage _mapStorage,是全局的静态变量,只是在AssociationsManager内部 可以使用,如果你把它放在外面一样可以使用,AssociationsManager有一个构造函数和一个析构函数。AssociationsManager manager就相当于调用了构造函数加锁的功能,AssociationsManager的作用域结束会自动调用析构函数进行解锁功能。AssociationsManager就是为了防止多线程访问出现混乱,AssociationsHashMap就是全局的静态变量获取的只调用一次

验证下AssociationsManager是否为单例

image.png

AssociationsManagerclass类型,此时的mmanager还没有初始化赋值,但是managerAssociationsManager类型。&manager变成是AssociationsManager*类型既指针类型, &manager存放的就是AssociationsManager的对象类型,&manager&manager1&manager2不同的地址说明实例化后的地址不是同的,不是同一个对象

验证下AssociationsHashMap &associations是否为单例

image.png 很明显AssociationsHashMap &associations是单例,只有单例才能值开辟一次内存只有一个地址,其它指针存的都是这一块地址

try_emplace 方法

下面用断点调试跟流程的方式进行探究

image.png

  template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    //根据key去查找对应的Bucket
    if (LookupBucketFor(Key, TheBucket))
      //通过make_pair生成相应的键值对
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); //false 表示往哈希关联表已经存在bucket

    //如果没有查询到 将数据插入bucket中,返回bucket
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    // 通过 make_pair生成相应的键值对
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);//true表示第一次往哈希关联表中添加bucket
  }
  • 首先去根据key去查找有没有对应的bucket,如果有将bucket进行封装返
  • 如果没有将去找空的bucket,如果空的bucket也没有就会去创建bucket,然后将数据存在bucketKey就是系统对object进行封装的结构,Args是传过来空的 ObjectAssociationMap{}对象关联表

LookupBucketFor探究

进入LookupBucketFor方法发现有两个相同的方法,源码如下

image.png

这俩两个方法的区别就是第二个参数一个带const修饰一个不带,很明显是第二个调用第一个方法。而且第二个参数值是指针类型,也就是常说的指针传递。

  • 首先根据key获取到的bucket地址存放在ConstFoundBucket指针中,然后将ConstFoundBucket指针的地址赋值给&FoundBucket指针中,这样FoundBucket存放的数据就会实时更新
  • 如果查询到就返回true
template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    //获取buckets的首地址
    const BucketT *BucketsPtr = getBuckets();
    //获取当前buckets的个数
    const unsigned NumBuckets = getNumBuckets();
    // 如果NumBuckets = 0 返回 false
    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false;
    }

    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    const KeyT EmptyKey = getEmptyKey();// 获取肯空bucket的key
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
           !KeyInfoT::isEqual(Val, TombstoneKey) &&
           "Empty/Tombstone value shouldn't be inserted into map!");
    //hash 获取下标和cache中的很像
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    unsigned ProbeAmt = 1;
    // 进行while循环
    while (true) {
      //根据下标找到对应的`bucket`内存偏移 ThisBucket = 首地址 + 第几个
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket?  If so, return it.
      // 如果查询到`bucket`的`key`和`Val`相等 返回当前的bucket说明查询到了
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // If we found an empty bucket, the key doesn't exist in the set.
      // Insert it and return the default value.
      //没有查询到获取一个空的bucket的 目的是可以向空的bucket插入数据
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // If we've already seen a tombstone while probing, fill it in instead
        // of the empty bucket we eventually probed to.
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }
      
      // If this is a tombstone, remember it.  If Val ends up not in the map, we
      // prefer to return it than something that would require more probing.
      // Ditto for zero values.
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
          !FoundTombstone)
        FoundTombstone = ThisBucket;  // Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
        FoundTombstone = ThisBucket;

      // Otherwise, it's a hash collision or a tombstone, continue quadratic
      // probing.
      if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      // BucketNo ++
      BucketNo += ProbeAmt++;
      //在hash获取下标
      BucketNo &= (NumBuckets-1);
    }
  }

LookupBucketFor下断点进行调试源码如下

image.png

因为是第一进行关联对象还没有创建关联对象表所以地址是空的,也没有bucket,所以返回false FoundBucket = nil

try_emplace方法中的InsertIntoBucket下断点,查看写TheBucket是否为nil

image.png

TheBucket确实为nil,下面进入插入流程

InsertIntoBucket

  Btemplate <typename KeyArg, typename... ValueArgs>
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
    //获取空的`bucket`
    TheBucket =  InsertIntoBucketImpl(Key, Key, TheBucket);
    //将key插入TheBucket中的key为first中
    TheBucket->getFirst() = std::forward<KeyArg>(Key);
    //如果的将value值插入TheBucket中的key为second中
    ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
    return TheBucket;
  }

核心功能获取空的bucket然后进行键值的匹配存储将keyvalue插入到bucket

InsertIntoBucketImpl

 
  template <typename LookupKeyT>
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // NewNumEntries 表示将要插入一个 bucket
    unsigned NewNumEntries = getNumEntries() + 1;
    //获取bucket总个数
    unsigned NumBuckets = getNumBuckets();
    //如果当前要插入的个数 大于等于总个数的3/4 进行两倍扩容
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);//进行两倍扩容,但是如果NumBuckets = 0 默认是开辟4个buckeet
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets();
    } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      //这种情况是负载小于1/8,这种情况比较复杂暂不讨论 很少会出现
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);

    //
    if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
      // Replacing an empty bucket.
      // 当前bucket被占用的数量 + 1
      incrementNumEntries();
      
    } else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
      // Replacing a tombstone.
      incrementNumEntries();
      decrementNumTombstones();
    } else {
      // we should be purging a zero. No accounting changes.
      ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
      TheBucket->getSecond().~ValueT();
    }
    return TheBucket;
  }

核心功能就是开辟内存然后获取空的bucket,开辟内存是this->grow(NumBuckets)获取空的bucket是通过LookupBucketFor,在this->grow(NumBuckets)下断点

image.png

进入两倍扩容流程grow(NumBuckets * 2),跟断点流程如下进入grow方法

void grow(unsigned AtLeast) {
    static_cast<DerivedT *>(this)->grow(AtLeast);
  }

断点进入grow(unsigned AtLeast)方法

  #define MIN_BUCKETS 4
  void grow(unsigned AtLeast) {
    unsigned OldNumBuckets = NumBuckets;
    BucketT *OldBuckets = Buckets;
    // MIN_BUCKETS = 4
    allocateBuckets(std::max<unsigned>(MIN_BUCKETS, static_cast<unsigned>(NextPowerOf2(AtLeast-1))));
    ASSERT(Buckets);
    if (!OldBuckets) {
      this->BaseT::initEmpty();
      return;
    }

    this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets);

    // Free the old table.
    operator delete(OldBuckets);
  } 
  • allocateBuckets方法就是根据bucket的类型和Num开辟的个数去开辟内存空间
  • bucket的个数是MIN_BUCKETSNextPowerOf2(AtLeast-1))取最大值。MIN_BUCKETS是一个宏值为4
  • BaseT::initEmpty()初始化空的bucket具体类型根据T的类型注意空的桶子keyfirst对应的值是1
  • moveFromOldBuckets将旧的buckets移动到新的buckets中,这点和cache扩容是不一样
  • 释放旧的buckets

断点进入NextPowerOf2方法

// 32位
/// NextPowerOf2 - 返回 2 的下一个幂(32 位)
/// 严格大于 A。溢出时返回零。
inline uint32_t NextPowerOf2(uint32_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  return A + 1;
}
// 64位
/// NextPowerOf2 - 返回 2 的下一个幂(64 位)
/// 严格大于 A。溢出时返回零。
inline uint64_t NextPowerOf2(uint64_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  A |= (A >> 32);
  return A + 1;
}

macOS中断点进入的是uint32_t NextPowerOf2方法,严格大于A,超过的话就返回0。且参数A是无符号整型。目前传来的参数是-1-1转换成二进制的规则是:1的原码=0x01,取反=0xfe,再加1=0xff,如果是32位,-1的二进制是0xffffffff。如果是64-1的二进制是0xffffffffffffffff,在进行A |= (A >> n),其实就是等于其本身,然后A+1溢出等于0,当然你也可以理解为-1 + 1 = 0,因为是两倍扩容,而二进制之间也是2倍,所以只要不超过范围就是等于传进来的值

断点进入initEmpty

 void initEmpty() {
    setNumEntries(0);
    setNumTombstones(0);

    ASSERT((getNumBuckets() & (getNumBuckets()-1)) == 0 &&
           "# initial buckets must be a power of two!");
    //设置空的key
    const KeyT EmptyKey = getEmptyKey();
    for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B)
      ::new (&B->getFirst()) KeyT(EmptyKey);
  }

断点进入getEmptyKeygetEmptyKey方法很多最后进入的方法如下

static inline DisguisedPtr<T> getEmptyKey() {
    return DisguisedPtr<T>((T*)(uintptr_t)-1);
  }

DisguisedPtr大家应该很熟悉,object也是封装成Disguis类型edPtr

   template** <typename T>
   class DisguisedPtr {
    uintptr_t value;
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }
    ...
}

ptr = (uintptr_t)-1,再次经过-(uintptr_t)ptr计算。就等于-(uintptr_t)((uintptr_t)-1) = 1 所以最后的value = 1,也就意味着空的bucketkeyfirst的值是value = 1

扩容完成以后开始调用LookupBucketFor(Lookup, TheBucket)Lookup就是封装好的object,作为参数传进来的

image.png

调用LookupBucketFor返回的是一个空的bucket。就是扩容以后创建的buckets中的一个.此时bucket的总个数是4

断点继续调试会进入incrementNumEntries()方法

image.png

incrementNumEntries()方法就是获取当前的有数据buckets的个数,之前是0,调用以后变成1

 void incrementNumEntries() {
    // getNumEntries() 是原来有数据的个数
    // getNumEntries() +1 表示将要插入的
    // 设置NumEntries的个数
    setNumEntries(getNumEntries() + 1); 
  }

源码显示将原有的NumEntries的数量加1incrementNumEntries()执行完以后返回bucket

TheBucketfirst赋值

//获取键值里面的first键
KeyT &getFirst() { return std::pair<KeyT, ValueT>::first; }

TheBucket->getFirst() = std::forward<KeyArg>(Key)Key赋值给TheBucket中的first

image.png

Key赋值给TheBucketfirst

TheBucketsecond赋值

image.png

TheBucketsecond默认可能有脏数据,将value赋值给second 赋值成功后返回通过make_pair封装好的bucket

isFirstAssociation 第一次关联

image.png

secound = truemake_pair方法中的第二个参数为true赋值的,意思就是第一次关联该对象

获取ObjectAssociationMap

image.png

根据disguised通过refs_result.first->second获取AssociationsHashMap表中的ObjectAssociationMap

插入association的数据或者是更新ObjectAssociationMap中的已有的数据

image.png

result.second = ture表示在ObjectAssociationMap中没有该数据是第一次存储,如果result.second = false表示ObjectAssociationMap中有该数据然后更细数据。此时存储的第一个属性是LWPerson类中的lw_name,存储的值是luwenhan

image.png

ObjectAssociationMap的结构和cache结构是比较相似,不仅可以存储值,还可以有其它的变量配合使用

设置isa中是关联对象属性

  ...
  if (isFirstAssociation)
        object->setHasAssociatedObjects()
  ...

isFirstAssociation第一次给关联对象设置值的时候才会调用setHasAssociatedObjects方法

objc_object::setHasAssociatedObjects()
{
    if (isTaggedPointer()) return;

    ...

    isa_t newisa, oldisa = LoadExclusive(&isa.bits);
    do {
        newisa = oldisa;
        if (!newisa.nonpointer  ||  newisa.has_assoc) {
            ClearExclusive(&isa.bits);
            return;
        }
        //将isa中的has_assoc 设置为true
        newisa.has_assoc = true;
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}

通过setHasAssociatedObjects方法设置对象存在关联对象,即isa指针has_assoc属性设置为true 最后通过releaseHeldValue方法释放旧值

关联对象存储流程图

image.png

关联对象 - get

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

objc_getAssociatedObject调用了_object_get_associative_reference进入_object_get_associative_reference方法,关联对象取值就是比较简单的了就是查表

id
_object_get_associative_reference(id object, const void *key)
{   
    //创建空的关联对象
    ObjcAssociation association{};

    {   //创建一个管理类
        AssociationsManager manager;
        //获取全局唯一的HashMap表
        AssociationsHashMap &associations(manager.get());
        // iterator是个迭代器实际上相当于找到objc_object和对应的ObjectAssociationMap
        // 类似{first = objc_object,second =ObjectAssociationMap}结构
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        
        //如果这个迭代器封装的一个结构不是最后一个进入判断流程
        if (i != associations.end()) {
            //获取 ObjectAssociationMap
            ObjectAssociationMap &refs = i->second;
            //获取 ObjectAssociationMap 的迭代器
            ObjectAssociationMap::iterator j = refs.find(key);
            //如果这个迭代器封装的一个结构不是最后一个进入判断流程
            if (j != refs.end()) {
                //获取 association
                association = j->second; 
                //retain 新值
                association.retainReturnedValue();
            }
        }
    }
    //release 旧值,返回新值
    return association.autoreleaseReturnedValue();
}

注释已经写的很清楚,就不在赘述一遍,直接调试查看最后的结果

image.png

关联对象其实就是一个查表取值的过程,然后返回存储的值。相比较关联对象存值,取值流程也更加容易理解

总结

关联对象的底层探究大家可能觉着不常用,没必要探究,其实探究过程你对苹果底层的封装思想以及哈希表都会有一个深刻的理解