iOS底层-18:类的拓展 & 关联对象 底层探索

类别和类拓展

1.category:类别 分类

  • 专门用来给类添加新的方法
  • 不能给类添加成员变量,添加了也无法取到
  • 分类中使用了@property给类添加属性,只会生成setter getter方法声明,没有实现,也不会生成带下划线的成员变量

    可以通过runtime关联对象给分类添加属性

2.extension:类拓展

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

1.在.m中写一个类拓展


@interface LRTeacher : NSObject

@end

@implementation LRTeacher

@end

@interface LRTeacher ()

@end

编译会报错:类的拓展不能在实现implementation之后

我们把类拓展写在interface类的声明之前

@class LRTeacher;
@interface LRTeacher ()

@end

@interface LRTeacher : NSObject

@end
@implementation LRTeacher

@end

还是会报错,对于不明确的类不能添加类拓展


结论:类拓展(extension)只能写在@interface之后@implemention之前

2.类拓展的本质
main.m中添加一下代码,然后用clang编译。


@interface LRTeacher : NSObject

@end
@interface LRTeacher ()
@property (nonatomic,copy) NSString * name;
@property (nonatomic,strong) NSArray * array;

- (void)instanceMethod;
- (void)classMethod;
@end
@implementation LRTeacher
- (void)instanceMethod {
    printf("%s",__func__);
}
- (void)classMethod {
    printf("%s",__func__);
}
@end

打开main.cpp文件,搜索LRTeacher

  • 属性生成了下划线成员变量,和setter、getter方法

总结:
extension中添加的方法和属性与在.h文件@interface中添加的并无二致。
编译时,自动把extension中的方法和属性并入了类中。

关联对象

category添加属性时,只是添加了getter setter的声明,并没有实现。
添加一个LRPersoncategory,在分类里添加一个属性cate_name;


@interface LRPerson : NSObject

@end

@implementation LRPerson

@end

@interface LRPerson (LR)

@property (nonatomic,copy) NSString *cate_name;

@end

@implementation LRPerson (LR)

@end

main.m中调用一下cate_namesetter方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        LRPerson *lr = [LRPerson alloc];
        lr.cate_name = @"LR";
    }
    return 0;
}

command+B编译成功
command+R运行时,发生了错误

-[LRPerson setCate_name:]: unrecognized selector sent to instance 0x10350a090

这充分说明了category中添加的属性,只是声明了getter setter,并没有实现。

  • 通过关联对象实现category中的getter setter
    category重写cate_namegetter setter方法
- (void)setCate_name:(NSString *)cate_name {
    objc_setAssociatedObject(self, @"cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name {
    return objc_getAssociatedObject(self, @"cate_name");
}

command+R运行程序,程序运行成功,并没有报错。

底层探索
  • setter方法处加上断点,运行程序

  • 点击进入objc_setAssociatedObject

  • 点击进入get()

  • 通过step into调试到下一步,进入了_base_objc_setAssociatedObject


    这是为什么呢?
    查看SetAssocHook的源码

    调用SetAssocHook实际上就是调用_base_objc_setAssociatedObject

  • 点击进入_object_set_associative_reference

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    //传入的object 和 value 有一个为空就返回
    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 disguised{(objc_object *)object};
    //包装了一下 policy 和 value ,初始化一个association
    ObjcAssociation association{policy, value};

    //当策略类似是retain或者copy的时候进行对应处理
    association.acquireValue();

    {
        //构造方法 初始化一个manager
        AssociationsManager manager;
        // 获取hashMap 全场唯一 
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //第一个参数是 对象包装后的结构
            //第二个参数 创建一个空的map 用来接收查找结果
            //try_emplace返回的是一个含有3个变量的结构 
            //通过对象在整个hashMap里查找该对象的关联map ObjectAssociationMap
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
               //第一次进来时 设置isa的has_assoc位
                object->setHasAssociatedObjects();
            }

            //获取该对象的关联map  ObjectAssociationMap
            auto &refs = refs_result.first->second;
            //在ObjectAssociationMap中通过key去查找
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            //如果传入的value是空值 擦除里面数据
            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();
}
  • try_emplace
 template 
  std::pair try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;//创建一个空的BucketT 来接收查找结果
    if (LookupBucketFor(Key, TheBucket)) //找到了的话,返回false
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    //没有找到的话就插入 返回true
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
  • 点击进入LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
template 
  bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;//接收查询结果
    bool Result = const_cast(this)
      ->LookupBucketFor(Val, ConstFoundBucket);//调用LookupBucketFor,参数不同 方法重载
    FoundBucket = const_cast(ConstFoundBucket);
    return Result;
  }
  • 点击进入LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const
 template
  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);
    }
  }
  • InsertIntoBucket
template 
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
    TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);

    TheBucket->getFirst() = std::forward(Key);
    ::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
    return TheBucket;
  }
  • InsertIntoBucketImpl
template 
  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;
  }
断点调试

_object_set_associative_reference如下断点开始

1.进入try_emplace查找插入对象对应的ObjectAssociationMap

2.进入bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)

3.进入bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const

没有找到返回false
4.回到bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)

此时打印:

  • ConstFoundBucket

  • FoundBucket赋值后打印

    image.png

  • Resultfalse

    image.png

5.回到try_emplace返回为false,会走InsertIntoBucket方法

6.进入InsertIntoBucket


打印此时的key 和 TheBucket

7.进入InsertIntoBucketImpl


又开始LookupBucketFor

8.进入bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)

9.进入bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const


返回false,打印FoundBucket

10.回到bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)

11.回到InsertIntoBucketImpl


打印TheBucketNumBuckets为4

12.调用incrementNumEntries

13.返回InsertIntoBucket
打印TheBucket


-getFirst()后,打印TheBucket,没有变化

  • getSecond()后,打印TheBucket,没有变化

14.返回try_emplace


打印TheBucket,没有变化

15.makeIteratormake_pair组装后返回_object_set_associative_reference

  • 返回值为ture,设置isa的关联对象位

  • refs赋值

  • 打印refs_result

  • 打印refs

16.进入try_emplace查找插入key对应的值ObjcAssociation
流程与前面大致相同


此时TheBucketObjcAssociation类型

17.LookupBucketFor返回false,回到try_emplace

18.开始插入InsertIntoBucket

  • 执行完InsertIntoBucketImpl,返回值为false
    打印TheBucket

    key、policy、value的值已经设置进了TheBucket

20.返回try_emplace,组装TheBucket

  1. 返回_object_set_associative_reference

    打印result

    关联属性成功,setter方法讲解完毕,下面我们分析getter方法
  • 点击进入objc_getAssociatedObject

  • 点击进入_object_get_associative_reference


id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};//创建一个空的ObjcAssociation
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());//得到全局唯一的AssociationsHashMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);//通过对象拿到该对象的关联map
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;//取到该对象的关联map  ObjectAssociationMap
            ObjectAssociationMap::iterator j = refs.find(key);//在对象的ObjectAssociationMap中通过key查找值
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();//执行retain操作,如果policy是retain的话
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

总结

设值流程
1.创建一个AssociationsManager管理类
2.获取全局唯一的静态哈希Map (AssociationsHashMap)
3.判断插入的关联值value是否存在
3.1 存在,走第4步
3.2 不存在,走关联对象插入空流程
4.创建一个空的 ObjectAssociationMap 去取查询的键值对
5.如果没有发现这个Key,就插入一个 空的 BucketT进去 返回
6.标记对象isa,存在关联对象
7.用当前的 policy和_value组成一个ObjcAssociation替换原来空的BucketT
8.根据policy判断,setter方法结束是否需要release

关联对象插入空流程
1.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator迭代查询器
2.清理迭代器
3.插入空值相当于清除

取值流程
1.创建一个AssociationsManager管理类
2.获取全局唯一的静态哈希Map (AssociationsHashMap)
3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator迭代查询器
4.如果这个迭代查询器不是最后一个,获取 ObjectAssociationMap (这里有policyvalue)
5.通过 ObjectAssociationMap拿到他的policy看是否需要retain

你可能感兴趣的:(iOS底层-18:类的拓展 & 关联对象 底层探索)