类扩展&关联对象

1.jpeg

我们在前面的文章讲到类和分类的加载原理,今天我们来探索下类扩展和关联对象。

在这之前我们下来看看类扩展和分类的区别:

1:category:类别、分类

· 专门用来给类添加新的方法
· 不能给类添加成员属性,添加了成员变量,也无法取到
· 注意:其实可以通过runtime给分类添加属性
· 分类中用 @property 定义变量,只会生成变量的gettersetter 方法的声明,不能生成方法实现和带下划线的成员变量。

2:extension:类扩展

· 可以说成是特殊的分类,也称作匿名分类
· 可以给类添加成员属性,但是是私有变量
· 可以给类添加方法,也是私有方法

类扩展

下面我们直接在main.m文件里写一个ZYTeacher类 并且写一个他的分类如下图:

2.png

所以分类是我们在开发中使用非常频繁的一个东西,下面我们利用clang命令clang -rewrite-objc main.m -o main.cpp将这个main.m文件转换成c++文件main.cpp,然后去查看下c++的实现是什么样的。

我们分别看下属性和方法:

3.png
4.png
5.png

我们可以发现类扩展的属性和方法都是存在的,实现都和类的属性方法一样,并没并没有出现像前面文章里分析分类category时候出现的分类特出处理情况。

那我们不禁要想一下,类扩展和分类与这样的区别那类扩展是否会像分类一样影响主类的加载呢?(因为前面我们发现分类添加load方法与否会影响主类的加载)我们探索下:

我们创建一个ZYPerson类实现几个方法,然后创建一个ZYPerson的类扩展文件命名为ZYPerson (EXTA)并且声明两个方法,然后将分类头文件引入到ZYPerson.m里最后实现分类声明的方法。我们来在objc818源码里运行这几个文件(在mian.m里调用方法),跟踪一下加载流程。

6.png

ZYPerson .h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson : NSObject

- (void)zyEatSugar;

+ (void)sayHappy;

- (void)zyShowTime;

@end

NS_ASSUME_NONNULL_END

ZYPerson .m

#import "ZYPerson.h"
#import "ZYPerson+EXTA.h"
@implementation ZYPerson
+(void)load{

}

- (void)zyEatSugar
{
    NSLog(@"%s",__func__);
}

+ (void)sayHappy
{
    NSLog(@"%s",__func__);
}

- (void)zyShowTime
{
    NSLog(@"%s",__func__);
}
- (void)zyT_extA_instanceMethod1
{
    NSLog(@"%s",__func__);
}
+ (void)zyT_extA_classMethod
{
    NSLog(@"%s",__func__);
}
@end

ZYPerson (EXTA).h

#import "ZYPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson ()
- (void)zyT_extA_instanceMethod1;
+ (void)zyT_extA_classMethod;
@end

NS_ASSUME_NONNULL_END

最后我们到main.m文件调用主类的方法:

7.png

我们同样效仿之前探索类加载的方法在之前添加特殊打印和断点的地方都加上。然后运行程序在每一个点去获取ro,看看什么时候类扩展的方法加载进去了:

8.png

结论:类扩展的方法在编译阶段就已经添加到了类里面也就是data()里面了。类扩展和分类是不一样的。

关联对象

我们知道分类/category不能添加属性/成员变量。开篇也有说明。并且给出了解决这个问题的方案就是利用runtime来实现添加属性。这也是我们熟悉的关联对象。那么关联对象的实现原理是怎么样的呢?我们来探究下:

直接上代码:
ZYPerson+ZYA.h

#import "ZYPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson (ZYA)
@property (nonatomic, copy) NSString *zyA_name;
@property (nonatomic, copy) NSString *zyA_age;

- (void)saySomething;

- (void)zyA_instanceMethod1;
- (void)zyA_instanceMethod2;


+ (void)zyA_classMethod1;
+ (void)zyA_classMethod2;

@end

NS_ASSUME_NONNULL_END

ZYPerson+ZYA.m

#import "ZYPerson+ZYA.h"
#import <objc/runtime.h>
@implementation ZYPerson (ZYA)

+(void)load
{
}
+ (void)zyA_classMethod1
{
    NSLog(@"%s",__func__);
}
+ (void)zyA_classMethod2
{
    NSLog(@"%s",__func__);
}

- (void)saySomething
{
    NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod1
{
    NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod2
{
    NSLog(@"%s",__func__);
}

/*
 * 关联对象
 **/
- (void)setZyA_name:(NSString *)zyA_name
{
    /**
     1: 对象
     2: 标识符
     3: value
     4: 策略
     */
    objc_setAssociatedObject(self, "zyA_nameKey", zyA_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)zyA_name
{
    return  objc_getAssociatedObject(self, "zyA_nameKey");
}
- (void)setZyA_age:(NSString *)zyA_age
{
    objc_setAssociatedObject(self, "zyA_ageKey", zyA_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)zyA_age
{
    return objc_getAssociatedObject(self, "zyA_ageKey");
}
@end

mian.m

#import <Foundation/Foundation.h>
#import "ZYPerson.h"
#import "ZYPerson+ZYA.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ZYPerson *person = [ZYPerson alloc];
        person.zyA_name = @"Wayne";
        person.zyA_age = @"20";

    }
    return 0;
}

探索:

我们直接到ZYPerson+ZYA.m文件关联对象方法里。直接command+点击查看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)
{
    // 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};
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        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 */
                isFirstAssociation = true;
            }

            /* 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);

                    }
                }
            }
        }
    }

    // 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.releaseHeldValue();
}

上面这个方法 总共60多行代码,我们稍微分析下看看哪些是重点。

1,首先传进来的参数:

object:关联对象(ZYPerson(ZYA));
key : 我们给属性设置的keyzyA_nameKey);
value :我们属性的值(Wayne);
policy : 策略 (存储的策略)

2,第一部分,头部代码:
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));

    //将 object 包装成 一个统一的 数据结构 ptr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //策略 policy 、 value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();
    //省略下方代码
}

这一段代码主要做了两件事:
第一件: DisguisedPtr<objc_object> disguised{(objc_object *)object};将传入的对象/object进行处理,变成格式统一的ptr数据结构形式。方便统一管理。
第二件: ObjcAssociation association{policy, value};将传入的策略/policy值/value进行处理,变成一个对象形式的association

3,第二部分,尾部代码:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// 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.releaseHeldValue();
}

这部分代码主要是做最后的释放处理,所以也不是我们寻找的目标。

4,第三部分,中间代码:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    bool isFirstAssociation = false;
    {
        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 */
                isFirstAssociation = true;
            }

            /* 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);

                    }
                }
            }
        }
    }
}

这一段代码是重点,因为从其他两部分代码分析知道所有的存储和获取逻辑都在这一段代码里。我们先在AssociationsHashMap &associations(manager.get());这行代码打一个断点然后运行程序让程序到这个方法里来。

9.png

从上图也可以看到lldb调试出来的前面包装的disguisedassociation的数据结构

#######方法:AssociationsManager manager;

我们再看看AssociationsManager manager;这句代码,这句代码的作用是调用构造函数。作用域就是上面代码的最外层{}。实现了在这个作用域每次来就调用一次并且上锁的作用,如下代码(构造函数和析构函数我们在补充里有例子解析)

方法:AssociationsManager

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

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

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

#######方法:AssociationsHashMap &associations(manager.get());

点击get进入到上方的AssociationsManager实现方法可以看到里面有这里两部分代码:

AssociationsHashMap &get() {
        return _mapStorage.get();
    }
static Storage _mapStorage;

方法:AssociationsHashMap

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

上方代码就是表明AssociationsHashMap 是一个单例,因为static Storage _mapStorage;表明是全局静态变量,这个地方的两句代码很容易让人搞混淆,容易让人觉得AssociationsManager是个单例然而恰恰相反 内部实现可以发现原来这个AssociationsHashMap 才是一个单例。每次AssociationsManager创建manager然后去操作HashMap的时候就去调用AssociationsHashMap这个单例保证每次进来调用的都是同一张表。

验证单例:

我们利用创建多个manager和多个associations打印地址来验证。
因为class AssociationsManager {}对于mananger创建有加锁,如果创建多个的话会导致崩溃那我们就去掉锁:

21.png
22.png

接下来我们看看assouciationsrefs_result的数据结构:

10.png

assouciations 数据类型比较简单,但是这个refs_result的类型是个什么鬼?这么长?幸好内容比较简单。那我们看看refs_result这个值的获取方法associations.try_emplace(disguised, ObjectAssociationMap{})

// Inserts key,value pair into the map if the key isn't already in the map.
  // The value is constructed in-place if the key is not in the map, otherwise
  // it is not moved.
  template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

这里传进来一个disguised和一个ObjectAssociationMap{},所以这里的key 是个对象也就是我们的object封装后的disguised。然后创建了一个TheBucket。然后调用了一个LookupBucketFor的方法传入这个keyTheBucket。我们看看这个LookupBucketFor方法,发现有两个:

第一个:

template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
//省略内部代码 太长了
}

第二个:

template <typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;
    bool Result = const_cast<const DenseMapBase *>(this)
      ->LookupBucketFor(Val, ConstFoundBucket);
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
    return Result;
  }

分析:从传入的参数TheBucket我们可以知道我们应该看第二个方法,因为第一个方法对于这个TheBucket的要求有const修饰。而我们在上面的方法看到创建的并没有。

我们从第二个 LookupBucketFor的处理方法可以知道最终还是调用了第一个方法的LookupBucketFor获取到Result值返回,同时需要注意的是这里传入的TheBucket是个指针传递,为什么呢?因为我们看这行代码:

FoundBucket = const_cast<BucketT *>(ConstFoundBucket);

这句代码做的处理就是把传进来的TheBucket/FoundBucket处理之后又进行了赋值。就是说他可以把值带回上一个方法。

所以我们下一步就要回到上面的第一个LookupBucketFor方法

方法:bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const

/// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in
  /// FoundBucket.  If the bucket contains the key and a value, this returns
  /// true, otherwise it returns a bucket with an empty marker or tombstone and
  /// returns false.
  template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    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();
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
           !KeyInfoT::isEqual(Val, TombstoneKey) &&
           "Empty/Tombstone value shouldn't be inserted into map!");

    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    unsigned ProbeAmt = 1;
    while (true) {
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket?  If so, return it.
      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.
      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 += ProbeAmt++;
      BucketNo &= (NumBuckets-1);
    }
  }

上面这个方法 一开始就是一些判断、创建变量之类的然后到下面这句代码:

unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);

这句代码其实作用就是利用hash 函数得到 bucketNobucket的下标 同cache_t 查找bucket 时候获取那个index一样。

然后就是下方的一个while(true){}的死循环。这个死循环就跟之前那个cache_t里查找bucketdo while循环一样的。根据查找结果最后出去回到方法template <typename... Ts> std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args)

方法:template <typename... Ts> std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args):

// Inserts key,value pair into the map if the key isn't already in the map.
  // The value is constructed in-place if the key is not in the map, otherwise
  // it is not moved.
  template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

找到了就走if然后return返回结果,如果没找到就走下面的代码

将上面创建的空的bucket调用方法InsertIntoBucket插入一个表。

11.png

方法:InsertIntoBucket

template <typename KeyArg, typename... ValueArgs>
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
    TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);

    TheBucket->getFirst() = std::forward<KeyArg>(Key);
    ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
    return TheBucket;
  }

方法:InsertIntoBucketImpl:

template <typename LookupKeyT>
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // If the load of the hash table is more than 3/4, or if fewer than 1/8 of
    // the buckets are empty (meaning that many are filled with tombstones),
    // grow the table.
    //
    // The later case is tricky.  For example, if we had one empty bucket with
    // tons of tombstones, failing lookups (e.g. for insertion) would have to
    // probe almost the entire table until it found the empty bucket.  If the
    // table completely filled with tombstones, no lookup would ever succeed,
    // causing infinite loops in lookup.
    unsigned NewNumEntries = getNumEntries() + 1;
    unsigned NumBuckets = getNumBuckets();
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets();
    } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);

    // Only update the state after we've grown our bucket space appropriately
    // so that when growing buckets we have self-consistent entry count.
    // If we are writing over a tombstone or zero value, remember this.
    if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
      // Replacing an empty bucket.
      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;
  }

在插入方法中也是按照3/4 两倍扩容的方式来插入,这里不做过多分析(跟前面的很多扩容一样)在插入之后就返回这个bucket:

12.png

然后就是去调用方法make_pair并且利用迭代器makeIterator来实现TheBucketgetBucketsEnd()配对。最后我们看到第二个参数返回的是个true。这个参数就是前面refs_result里的refs_result.second。用来if判断的。

13.png

到这里我们disguised已经做了处理,但是至此,我们保存valueassociations还没有做处理和关联,所以有了下面的步骤

14.png

然后利用refs.try_emplace(key, std::move(association));keyassociation做关联.

15.png
20.png

我们可以看到上图的LookupBucketFor的判断还是为false不走if。因为此时打印的TheBucket是我们前面创建的一个空的桶子所以不走if,看图

16.png

所以会走下面的insert,然后就走了上面创建桶子插入空桶子同样的流程就不做重复解释了。最后返回一个结果 到上一个方法:

17.png

流程总结:
1,_object_set_associative_reference方法被调用传进来一个对象object、一个键key、一个值value、一个策略policy
2,先对对象object做处理包装成一个统一格式的disguisedDisguisedPtr<objc_object> disguised{(objc_object *)object};
3,对值value策略policy进行处理包装成一个associationObjcAssociation association{policy, value};
4,构造一个函数manangerAssociationsManager manager;
5,创建一个AssociationsHashMap的单例查找到一个总的hash表AssociationsHashMap &associations(manager.get());
6,对disguised进行处理并判断是否为第一次进来。 auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});查看bucket存在不不存在就创建一个空的bucket返回(第一次进来),存在就直接返回。
7,将封装好的associationkey进行关联,插入到bucket里。auto result = refs.try_emplace(key, std::move(association));

总结:

关联对象: 设值流程

1: 创建⼀个 AssociationsManager 管理类
2: 获取唯⼀的全局静态哈希Map
3: 判断是否插⼊的关联值是否存在:
3.1: 存在⾛第4步
3.2: 不存在就⾛ : 关联对象插⼊空流程
4: 创建⼀个空的 ObjectAssociationMap去取查询的键值对
5: 如果发现没有这个key就插⼊⼀个 空的 BucketT进去 返回
6: 标记对象存在关联对象
7: ⽤当前 修饰策略 和 值 组成了⼀个ObjcAssociation替换原来 BucketT 中的空
8: 标记⼀下 ObjectAssociationMap的第⼀次为false

关联对象插⼊空流程:

1: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
2: 清理迭代器
3: 其实如果插⼊空置 相当于清除

关联对象: 取值流程:

1: 创建⼀个 AssociationsManager 管理类
2: 获取唯⼀的全局静态哈希Map
3: 根据DisguisedPtr找到 AssociationsHashMap 中的 iterator迭代查询器
4: 如果这个迭代查询器不是最后⼀个 获取 : ObjectAssociationMap(这⾥有策略value)
5: 找到ObjectAssociationMap的迭代查询器获取⼀个经过属性修饰符修饰的value
6: 返回_value

总结: 其实就是两层哈希map , 存取的时候两层处理(类似⼆位数组)
关联对象总结图:
18.jpg
19.jpg

补充

构造函数、析构函数

这里直接写了个例子,可以直观的明白构造函数和析构函数的结构以及调用:

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

推荐阅读更多精彩内容