iOS 底层原理探索 之 关联对象

2,073 阅读9分钟
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载

以上内容的总结专栏


细枝末节整理


前言

之前的两片文章,我们分别研究了一下 类的加载 和 分类的加载 的一个整体流程。其实,其探索的都是一个 从 代码编译MachO内存 的一个过程。在内存中,会将所有的类映射到一张表里,之后可以将mechod_list调用出来。我们探索了类和分类的内容,那么今天来探索下类的扩展这部分的内容。

分类 category

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

类扩展 extension

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

extension 底层源码探索

定义一个 SMStudent 类 ,定义其实现,以及扩展部分:

@interface SMStudent : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)study;
- (void)askTeacher;

@end


@interface SMStudent ()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, copy) NSString *ext_schoolName;

- (void)ext_Study;
- (void)ext_gotoSchool;

@end


@implementation SMStudent

- (void)study {
    
}

- (void)askTeacher {
    
}

- (void)ext_Study {
    
}

- (void)ext_gotoSchool {
    
}

@end

常规操作一下 : clang -rewrite-objc main.m -o main-ext.cpp

接着打开编译好的 main_ext.cpp 文件, 搜索下 扩展中的 _ext_name,找到了我们想看到的:

extern "C" unsigned long OBJC_IVAR_$_SMStudent$_name;
extern "C" unsigned long OBJC_IVAR_$_SMStudent$_age;
extern "C" unsigned long OBJC_IVAR_$_SMStudent$_ext_name;
extern "C" unsigned long OBJC_IVAR_$_SMStudent$_ext_schoolName;
struct SMStudent_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	NSString *_name;
	NSString *_ext_name;
	NSString *_ext_schoolName;
};

...

static NSString * _I_SMStudent_ext_name(SMStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SMStudent$_ext_name)); }

static void _I_SMStudent_setExt_name_(SMStudent * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMStudent, _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[20];
} _OBJC_$_INSTANCE_METHODS_SMStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	20,
	{{(struct objc_selector *)"study", "v16@0:8", (void *)_I_SMStudent_study},
	{(struct objc_selector *)"askTeacher", "v16@0:8", (void *)_I_SMStudent_askTeacher},
	{(struct objc_selector *)"ext_Study", "v16@0:8", (void *)_I_SMStudent_ext_Study},
	{(struct objc_selector *)"ext_gotoSchool", "v16@0:8", (void *)_I_SMStudent_ext_gotoSchool},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_SMStudent_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SMStudent_setName_},
	{(struct objc_selector *)"age", "i16@0:8", (void *)_I_SMStudent_age},
	{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_SMStudent_setAge_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SMStudent_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SMStudent_setExt_name_},
	{(struct objc_selector *)"ext_schoolName", "@16@0:8", (void *)_I_SMStudent_ext_schoolName},
	{(struct objc_selector *)"setExt_schoolName:", "v24@0:8@16", (void *)_I_SMStudent_setExt_schoolName_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_SMStudent_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SMStudent_setName_},
	{(struct objc_selector *)"age", "i16@0:8", (void *)_I_SMStudent_age},
	{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_SMStudent_setAge_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SMStudent_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SMStudent_setExt_name_},
	{(struct objc_selector *)"ext_schoolName", "@16@0:8", (void *)_I_SMStudent_ext_schoolName},
	{(struct objc_selector *)"setExt_schoolName:", "v24@0:8@16", (void *)_I_SMStudent_setExt_schoolName_}}
};

static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_SMStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	2,
	{{"name","T@\"NSString\",C,N,V_name"},
	{"age","Ti,N,V_age"}}
};

显然,类扩展和分类是不同的,其定义的属性会生成 setter、getter 方法,且已经添加到了 类信息中。 下面我们使用objc源码看下流程,我们添加一个SMPerson的类扩展,开始探索:

image.png

image.png

注意此处的堆栈信息:

map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift(非懒加载分支)

总结

类扩展中的属性和方法是在编译期作为类的一部分编译到类的信息中去; 而,分类则更多的是为了动态性。

关联对象

因为分类无法添加属性,所以即使有调用 没有报错,即使build success,在运行时还是会奔溃,那么,我们就需要通过关联对象来对分类中的属性做一下关联。代码如下:

@interface SMPerson (Like)

@property (nonatomic, copy) NSString *like_name;
@property (nonatomic, assign) int like_time;

@end

@implementation SMPerson (Like)

- (void)setLike_name:(NSString *)like_name {
    
    objc_setAssociatedObject(self, "like_name", like_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

@end

接下来,我们看一下 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)
{
    // 这段代码用于在对象和键传递nil时工作。一些代码
    // 大概就是靠这个才不会撞车。检查并显式地处理它。
    // 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};

    // 在锁外保留新值(如果有的话)。
    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) {
                /* 这是我们建立的第一个联想 */
                isFirstAssociation = true;
            }

            /* 建立或替换关联 */
            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);

                    }
                }
            }
        }
    }

    // 在锁外部调用setHasAssociatedObjects,因为这个
    // 将调用对象的_noteAssociatedObjects方法,如果是
    // 有一个,这可能触发+初始化,那会怎样呢
    // 任意的东西,包括设置更多的关联对象
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // 释放旧值(锁外)
    association.releaseHeldValue();
}

forbidsAssociatedObjects

// 类不允许在其实例上关联对象
#define RW_FORBIDS_ASSOCIATED_OBJECTS       (1<<20)

bool forbidsAssociatedObjects() {
        return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
    }

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

下面我们根据 AssociationsManager 部分的代码 来整理下 AssociationsHashMap 的数据结构:

AssociationsHashMap

myNoteBase_副本.001.jpeg

try_emplace

  // 如果键尚未在映射中,则将键值对插入映射中
  // 如果键不在映射中,则就地构造值,否则
  // 它没有移动
  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); // 已经在表上

    // 否则,插入新元素
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

下面我们重点看下 try_emplace 的一个流程:

  /// LookupBucketFor—为Val查找适当的bucket,并将其返回
  /// FoundBucket。如果bucket包含键和值,则返回
  /// true,否则返回一个带有空标记或墓碑和的桶
  /// 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 -跟踪我们是否在探测时找到了一个墓碑
    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;
      // 发现Val的桶吗?如果是,返回它
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // 如果我们发现一个空的桶,则该键不存在于集合中
      // 插入它并返回默认值
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // 如果我们在探测的时候已经看到了墓碑,那就把它填上
        // 我们最终探测到的空桶
        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;

      // 否则,它是一个哈希冲突或墓碑,继续二次
      // 探索
      if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      BucketNo += ProbeAmt++;
      BucketNo &= (NumBuckets-1);
    }
  }

  // 虽然两个方法名字都一样,但是,通过参数不同,可以发现方法内部调用了上面的  LookupBucketFor 
  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;
  }

获取关联对象值

objc_getAssociatedObject

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

_object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        // 通过 object 找到 ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            // 通过 key 找到 ObjcAssociation
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

关联对象总结

通过以上分析,可以知道关联对象处理过程中的数据结构关系,有一个全局的数据集合 AssociationHashMap ,内部存储了全部的关联数据,一个对象会对应一个 ObjectAssociationMap,而该对象所关联的属性,都已键值对的形式存储在 ObjectAssociationMap中。

关联对象的销毁

在对象被销毁时会调用 dealloc 方法,同时 会对 关联对象进行释放。 rootDealloc->object_dispose->objc_destructInstance

objc_destructInstance

/***********************************************************************
* objc_destructInstance
* 在不释放内存的情况下销毁实例
* c++调用析构函数
* 调用ARC ivar清理
* 删除关联的引用
* 返回的obj。如果' obj '为nil,则不执行任何操作
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // 一次读取所有标志以提高性能
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // 这个顺序很重要
        if (cxx) object_cxxDestruct(obj);
        // 删除关联的引用
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

_object_remove_assocations

// 与设置/获取相关引用不同
// 这个函数是性能敏感的,因为
// 不能跟踪的原始isa对象(如OS对象)
// 是否有关联的对象
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);

            //如果我们没有解除分配,那么SYSTEM_OBJECT关联将被保留
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // 在正常的关联之后才会被释放
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // 释放(锁外的)所有东西
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

总结

设值流程

  • 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 , 存取的时候两层处理(类似二位数组)