iOS Kit

类扩展 与 关联对象的底层原理探索

2021-03-01  本文已影响0人  远方竹叶

类扩展、分类的区别

1. Category(分类或者类别)

2. Extension(类扩展)

类扩展的底层原理

类的扩展有两种创建方式

单独创建好的类扩展文件如下

类扩展的本质

有两种方法可以探究:clang源码

通过 clang 底层编译

下面我们通过 clang 底层编译,查看类扩展的底层实现

@interface LGPerson : NSObject

- (void)instanceMethod;
+ (void)classMethod;

@end

@interface LGPerson ()

@property (nonatomic, copy) NSString *lg_name;

- (void)ext_instanceMethod;
+ (void)ext_classMethod;

@end

@implementation LGPerson

- (void)instanceMethod {
    
}

+ (void)classMethod {
    
}

- (void)ext_instanceMethod {
    
}

+ (void)ext_classMethod {
    
}

@end
通过 objc 源码探索
#import "LGPerson.h"

@interface LGPerson ()

@property (nonatomic, copy) NSString *ext_name;

- (void)ext_intanceMethod3;
- (void)ext_intanceMethod4;

@end
/* ------ LGPerson.h ------*/
@interface LGPerson : NSObject

- (void)lc_intanceMethod1;

- (void)lc_intanceMethod2;

- (void)lc_intanceMethod3;

- (void)lc_intanceMethod4;

@end

/* ------ LGPerson.m ------*/
@implementation LGPerson

+ (void)load {
    
}

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

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

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

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

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

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

@end

总结

关联对象的底层原理

主要分为两部分:

在分类中添加属性 cate_name,通过 runtime 的关联属性方法重写它的 set、get 方法,并在 main 函数中调用如下

@interface LGPerson : NSObject

@end

@implementation LGPerson

@end

@interface LGPerson (LG)

@property (nonatomic, copy) NSString *cate_name;

@end

@implementation LGPerson (LG)

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

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGPerson *p = [[LGPerson alloc] init];
        p.cate_name = @"lg_lc";
        NSLog(@"%@", p.cate_name);
        
    }
    return 0;
}

关联对象的设值流程

  1. main 函数中 cate_name 赋值处和分类的 setCate_name 方法中打个断点,运行程序
  1. 继续往下运行

其中 objc_setAssociatedObject 方法有四个参数:

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
  1. 进入 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

进入 _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封装成一个数组结构类型,类型为DisguisedPtr
    DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
    // 包装一下 policy - value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();//根据策略类型进行处理
    //局部作用域空间
    {
        //初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
        AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
    
        AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
            if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
                /* it's the first association we make 第一次建立关联*/
                object->setHasAssociatedObjects();//nonpointerIsa ,标记位true
            }

            /* establish or replace the association 建立或者替换关联*/
            auto &refs = refs_result.first->second; //得到一个空的桶子,找到引用对象类型,即第一个元素的second值
            auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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);

                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();//释放
}

通过以上源码可知,_object_set_associative_reference 方法主要分为以下几部分:

源码调试流程
AssociationsManager()   { AssociationsManagerLock.lock(); }
~AssociationsManager()  { AssociationsManagerLock.unlock(); }

加锁并不代表唯一,只是为了避免多线程重复创建,可以在外层定义多个 AssociationsManager 变量的

// 定义变量
AssociationsHashMap &associations(manager.get());

进入 manager.get() 源码,如下

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

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

从源码中我们可以得知,_mapStorage 是一个静态变量,静态变量又是全局唯一的,AssociationsHashMap 是从静态变量 _mapStorage 中获取的,所以 AssociationsHashMap 是全局唯一的

运行项目,走到断点处,打印变量的数据结构

继续向下执行,此时传入的 value 有值,所以走 if 流程(如果传入的 value 为空,就会走 移除关联函数 流程,即 else 流程),查看 refs_result 的数据结构,类型很长,可以进行拆解

// pair 表示有键值对
(std::pair<
 objc::DenseMapIterator<DisguisedPtr<objc_object>,
 
 objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
 
 objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
 
 objc::DenseMapInfo<DisguisedPtr<objc_object> >,
 
 objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
 
 false>,
 
 bool>)
 
 👇
 
 // 简写
 (std::pair<
 
 objc,
 
 bool>)
try_emplace 源码

进入 try_emplace 源码实现,如下

从源码可以看到,有两个返回:

** LookupBucketFor 源码**

LookupBucketFor 源码有两个同名方法,区别是第一个方法中第二个参数有 const 修饰,通过 try_emplace 源码可知,调用的是第二个方法(重载函数),而第二个方法内部实现是调用第一个 LookupBucketFor 方法

继续往下走,运行至 TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);,查看此时的 TheBucket

可以看到 TheBucket 的类型与 refs_result 中属性的类型是一致的

** 两次 try_emplace 的区别**

继续执行,断点在 object->setHasAssociatedObjects();,源码如下

由此可知,通过 setHasAssociatedObjectsnonpointerIsahas_assoc 标记为 true,到此就将属性与 value 关联上了

关联对象设值流程图

从上面分析可以得出,关联对象的哈希 map 结构如下

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

关联对象的取值流程

继续执行下一步,断点来到重写分类的属性 get 方法,进入 objc_getAssociatedObject 源码的实现

_object_get_associative_reference

其源码实现如下:

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

    {
        AssociationsManager manager; // 创建一个 管理类
        AssociationsHashMap &associations(manager.get()); // 获取全局唯一的静态哈希 map
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 根据 object 查找 AssociationsHashMap,即获取 buckets
        if (i != associations.end()) { // 如果迭代器不是最后一个
            ObjectAssociationMap &refs = i->second; // 找到 ObjectAssociationMap 的迭代查询器,获取一个经过属性修饰符修饰的 value
            ObjectAssociationMap::iterator j = refs.find(key); //根据 key 查找 ObjectAssociationMap,即获取 bucket
            if (j != refs.end()) {
                association = j->second; // //获取 ObjcAssociation
                association.retainReturnedValue(); // retain 处理
            }
        }
    }

    return association.autoreleaseReturnedValue(); // 返回 value
}

通过以上源码,可以将取值流程分为以下几部分:

调用流程

总结

综上所述,关联对象的底层调用,主要就是两层哈希 map 的处理,即存取时都是两层处理。流程如下图所示

上一篇 下一篇

猜你喜欢

热点阅读