OC底层原理探索—扩展和关联对象

这篇文们来讲解下扩展和关联对象

扩展分析

image.png
  • 首先我们先在main.m文件中实现扩展
  • 然后clang -rewrite-objc main.m -o main.cpp生成main.cpp文件,来查看下底层实现
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
    NSString *_age;
    NSString *_ext_name;
};
static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }

static NSString * _I_Person_age(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, NSString *age) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _age), (id)age, 0, 1); }

static NSString * _I_Person_ext_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_ext_name)); }
static void _I_Person_setExt_name_(Person * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _ext_name), (id)ext_name, 0, 1); }

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[14];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    14,
    {{(struct objc_selector *)"hello", "v16@0:8", (void *)_I_Person_hello},
    {(struct objc_selector *)"hello_ext", "v16@0:8", (void *)_I_Person_hello_ext},
    {(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
    {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
    {(struct objc_selector *)"age", "@16@0:8", (void *)_I_Person_age},
    {(struct objc_selector *)"setAge:", "v24@0:8@16", (void *)_I_Person_setAge_},
    {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Person_ext_name},
    {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Person_setExt_name_},
    {(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
    {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
    {(struct objc_selector *)"age", "@16@0:8", (void *)_I_Person_age},
    {(struct objc_selector *)"setAge:", "v24@0:8@16", (void *)_I_Person_setAge_},
    {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Person_ext_name},
    {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Person_setExt_name_}}
};

  • main.cpp文件我们看出,扩展中实现的属性方法是和主类放在一起的,并没有像分类那样,生成类似于category_t的结构体

加载分析

准备工作
image.png

image.png

首先声明一个扩展文件,并在realizeClassWithoutSwift方法中打个断点

image.png

由上图的count可以看出有8个方法,我们大体猜测到扩展中的方法是和本类的方法存放在一起的,都存放在ro中,接下来我们继续打印来验证我们的猜测
image.png

这打印出来的方法就是我们实现的方法和属性的setter、getter方法

【结论】:扩展是在编译期就已经加载好的,和本类是存放在一起的

扩展和分类对比

分类:
  • 用来给类添加新方法或者重写本类中的方法
  • 不能给类添加成员变量,需要使用runtime中的关联对象来添加
  • 分类中用@property定义变量,只会生成变量的getter、setter方法的声明,不能生成方法实现和带下划线的成员变量。
  • 分类的加载发生在运行时
扩展
  • 是一个特殊的分类,也称作匿名分类
  • 可以给本类添加成员变量、属性、方法,不过添加的这些都是 私有的
  • 扩展的加载发生在编译期

关联对象

我们知道分类声明属性添加属性,是不会生成 成员变量的,于是这个问题我们来通过关联对象解决

我们先创建一个分类

@interface Person (SH)

@property (nonatomic,copy)NSString *cat_name;

@end

#import "Person+SH.h"
#import 
@implementation Person (SH)

- (void)setCat_name:(NSString *)cat_name{
    objc_setAssociatedObject(self, @"cat_name", cat_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cat_name{
    return objc_getAssociatedObject(self, @"cat_name");
}

进入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));
    //object:关联对象。 key:标记。value:关联对象的值 policy:策略(就是strong copy什么的)
    
    // 将object 统一包装成 DisguisedPtr 这种数据结构
    DisguisedPtr 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 manager;
        AssociationsHashMap &associations(manager.get());
  ....
        }
    }
}

    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }


class ObjcAssociation {
    uintptr_t _policy;
    id _value;
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}
    ObjcAssociation(const ObjcAssociation &other) = default;
    ObjcAssociation &operator=(const ObjcAssociation &other) = default;
....
}

image.png

我们在这给cat_name赋值

image.png

我们看到这个ptr就是我们的Person类的对象,这里统一将person对象统一包装成DisguisedPtr这个数据结构

image.png

这里看到将value和policy统一封装成ObjcAssociation数据结构

AssociationsManager和AssociationsHashMap

在讲这里之前,先补充一个小知识点

image.png
  • 当我们调用SHObjc的时候,会自动调用构造函数
  • autoreleasepool作用域结束时,会自动嗲用析构函数

接下来我们来分析AssociationsManager源码

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

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

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

    static void init() {
        _mapStorage.init();
    }
};
  • 在这我们发现AssociationsManager并不是一个单例,而是通过构造函数加锁、析构函数解锁,来达到线程安全。
  • AssociationsManager只是用来调用AssociationsHashMap的而已,AssociationsHashMap是一个单例,因为它通过_mapStorage.get()获取,_mapStorage是一个全局静态变量,放在任何地方都是唯一的。

接下来我们来验证下到底哪一个是全局单例


image.png
  • 由上图的 lldb打印我们得出结论AssociationsManager不是单例
  • AssociationsHashMap 是单例

流程分析

image.png

image.png

分析下这个数据结构$1,这里面有两个数据firstsecondsecond是一个布尔类型first是个对值,包含Ptr和End

接下来我们来研究下这段代码

auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});

首先传入的是disguised也就是我们的object,还有个ObjectAssociationMap

进入try_emplace函数

image.png

  • BucketT *TheBucket;创建一个空的BucketT
  • 调用 LookupBucketFor(Key, TheBucket),传入keydisguised,和TheBucket

进入LookupBucketFor函数

image.png

  • 注意这里有两个LookupBucketFor函数,我们这里先调用是第二个参数没有const 修饰的,这里先调用下面的函数
  • 函数内部又去调用上面的LookupBucketForFoundBucket是指针传递,也就是说在函数内部改变,函数外部的参数也会跟着改变

进入上面的LookupBucketFor函数

 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!");
// Val 是 disguised(包装的对象), hash 函数得到下标
   // 根据对象进行hash下标的计算
   unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
   unsigned ProbeAmt = 1;
//while 是死循环 查找位置
//找到了 对FoundBucket赋值,返回true
//找不到 再hash查找
   while (true) {
     //开启循环,下标平移,根据下标寻找bucket
     const BucketT *ThisBucket = BucketsPtr + BucketNo;
     // Found Val's bucket?  If so, return it.
     // 若果说bucket和val(object),如果说键值比对相等,则返回true,并将形参赋值
     if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
       FoundBucket = ThisBucket;
       return true;
     }

     //如果没有找到 同样讲形参赋值,返回false
     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 (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
         !FoundTombstone)
       FoundTombstone = ThisBucket;  // Remember the first tombstone found.
     if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
       FoundTombstone = ThisBucket;

     //小标平移 再hash
     if (ProbeAmt > NumBuckets) {
       FatalCorruptHashTables(BucketsPtr, NumBuckets);
     }
     BucketNo += ProbeAmt++;
     BucketNo &= (NumBuckets-1);
   }
 }
  • 这里通过getHashValue(Val) & (NumBuckets-1)哈希函数,计算出bucketNo的下标
  • while循环,根据下标找位置,找到位置将地址赋值给FoundBucket,当前我们这个地方返回的false,没有找到位置

回到try_emplace函数

image.png

  • 当前TheBucket是存放在AssociationsHashMap中,但是TheBucket没有东西,所以这时我们往里面插入内容
  • 调用InsertIntoBucket,传入TheBucket,传入key也就是disguised,传入Args也就是ObjectAssociationMap{}

进入InsertIntoBucket函数

  template 
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
//进行扩容
    TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
  // 将Key也就是disguised 赋值到 TheBucket 的 first
    TheBucket->getFirst() = std::forward(Key);
 // 将 ObjectAssociationMap{} 赋值到 TheBucket 的 second
    ::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
    return TheBucket;
  }

回到_object_set_associative_reference

image.png

  • 这个时候已经看到了const void *, objc::ObjcAssociation数据结构,这个属于ObjectAssociationMap的数据结构
  • 这个时候value、policy还没有存,还在association里。
image.png
  • 这里的refs_result.first是获取bucketbucket.second获取的是上面创建的ObjectAssociationMap
  • 接下来又是调用try_emplace函数,传入key(这里是属性名称),和association。将它们以键值对的方式存储到refs的bucket中,到此两层哈希存储完成。

关联对象插入nil 流程

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

                    }
                }
            }
  • associations也就是AssociationHashMap中查找disguised
  • 如果没有则进行 erase 清空操作

关联对象:设值流程

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

你可能感兴趣的:(OC底层原理探索—扩展和关联对象)