OC底层原理14-类扩展及分类的关联对象

iOS--OC底层原理文章汇总

在前面的文章中,探究了类的加载以及分类在懒加载和非懒加载的情况。本文探究一下分类另外一个“表兄弟”——类扩展。

类扩展

以下的形式就是类扩展

// 分类
@interface LGTeacher : NSObject
- (void)instanceMethod;
- (void)classMethod;
@end
// 类扩展
@interface LGTeacher ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
@implementation LGTeacher
- (void)ext_instanceMethod{
    
}
- (void)ext_classMethod{
    
}
- (void)instanceMethod{
    
}
- (void)classMethod{
    
}
@end

最常见的就是ViewController.m中的以下

类扩展

在类扩展里面,就是编写:成员变量、属性、私有方法,可以当成一个头文件xx.h去理解。
clang该文件,clang -rewrite-objc main.m -o main.cpp得到main.cpp分析之,则可以得到类扩展相关信息

  • 属性
    成员变量/setter/getter

    可以看到ext_nameC++源码层面是为带下划线的_ext_name,也有其setter、getter方法的实现。
  • 方法
    C++代码实现

    在类扩展的方法列表中,经过编译,方法以及添加到了 methodlist中,作为类的一部分。所以我们可以知道编译时期类扩展的方法直接添加到本类里面

再深入研究下类扩展在底层源码中的逻辑,打开一份objc源码工程,编写一个类扩展,实现以下

@interface LGPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end

类扩展的方法

可以知道类扩展的方法,作为类的信息在readClass过程中就已经编译到类信息中

分类与类扩展区别

分类

  • 1.专门用来给类添加新的方法;
  • 2.不能给类添加属性,添加了属性,也无法取到(包括点.语法、getter);
  • 3.分类中可以使用@property修饰变量,会生成setter、getter方法,但不会有带下划线成员变量以及方法实现;
  • 4.可通过runtime 给分类添加属性,即属性关联,重写setter、getter方法。

类扩展

  • 1.可理解为匿名的分类,是一种特殊分类;
  • 2.为类添加属性及方法,但都是私有属性、私有方法。

关联对象:底层原理

由前面知识知道,分类不能添加属性而直接获取到,但是可以在运行时动态添加关联对象,重写添加属性的setter、getter方法

@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}
- (void)setCate_name:(NSString *)cate_name{
    /**
     1: 关联的对象
     2: 标识符
     3: value
     4: 策略
     */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
    /**
    1.对象
    2.标识符
    */
    return  objc_getAssociatedObject(self, "cate_name");
}
@end

在分类中定义了一个cate_name,重写它的setter、getter方法,并添加关联对象。在这个情况下,它就可以通过获取cate_name的设值和取值。
老方法,研究源码,打开一个objc工程,Jump To Definition, 会有这样一个实现


/**********************************************************************
* Associative Reference Support
**********************************************************************/

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

static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}

static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};

void
objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue,
                                 objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) {
    SetAssocHook.set(newValue, outOldValue);
}

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}

通过一层一层的封装,我们找到了_object_set_associative_reference + _object_get_associative_reference这两个方法,

设值:_object_set_associative_reference

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    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.
    //如果value存在,根据policy的值对value值作相应的操作。
    association.acquireValue();

    {//局部作用域----关键代码
        //这不是一个单例,manager在构造时加锁,析构时开锁。范围为该作用域
        AssociationsManager manager;   
        //DenseMap<DisguisedPtr<objc_object>, DenseMap<const void *, ObjcAssociation>> 就是个嵌套的的DenseMap, manager.get()操作的是一个static的_mapStorage变量,而associations的数据是从manager.get()初始化来的,而且manager.get()返回的是带&的引用,所以associations也可以看做是静态的。
        AssociationsHashMap &associations(manager.get());

        if (value) {
            
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                //判断disguised为key的桶是新插入进来的,根据条件设置isa_t中的has_assoc位为true
                object->setHasAssociatedObjects();
            }

            //找到associations中的disguised对应的ObjectAssociationMap表
            auto &refs = refs_result.first->second;
            //用key在ObjectAssociationMap表中查找,如果表中不存在该key那么就把key和association对应插入到ObjectAssociationMap中
            auto result = refs.try_emplace(key, std::move(association));
            //result.second为false, 说明ObjectAssociationMap表中原来已有该key,不会移动,所以这里进行了swap的操作来交换association的值。
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {   //value为nil, 取消关联。
            //先从associations表中找到disguised对应的ObjectAssociationMap表,又用pair包装后返回。
            auto refs_it = associations.find(disguised);
            //判断是否从associations表中找到了disguised对应的ObjectAssociationMap表
            if (refs_it != associations.end()) {
                //从pair中拿到ObjectAssociationMap表
                auto &refs = refs_it->second;
                //从ObjectAssociationMap表中查找key对应的association,然后把它作为DenseMapIterator的位置指针初始化后返回
                auto it = refs.find(key);
                if (it != refs.end()) {
                    //把要擦除的association记录下来,再进行releaseHeldValue
                    association.swap(it->second);
                    //从ObjectAssociationMap表中擦除association以及其他相应的操作
                    refs.erase(it);
                    if (refs.size() == 0) {
                        //从associations表中擦除ObjectAssociationMap表
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

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


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

// class AssociationsManager manages a lock / hash table singleton pair.
class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;  //静态变量

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    //注意这里的&,返回的是引用
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
    //map_images_nolock中第一次调用时,通过arr_init调用此方法进行_mapStorage初始化
    static void init() {
        _mapStorage.init();
    }
};

构造时加锁,析构时开锁。为了对象的安全,同时能防止冲突

AssociationsManager manager;  
等价于==>
AssociationsManager();
lock();
//TODO: Something
unlock();//作用域之后unlock

取值:_object_get_associative_reference

id _object_get_associative_reference(id object, const void *key)
{   
    //先初始化一个用来接收值的association
    ObjcAssociation association{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        //用object作为key从associations表中找到对应的ObjectAssociationMap表
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            //用key在ObjectAssociationMap表中搜索对应的ObjcAssociation
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                //找到后赋值给association,然后retain
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

总结以上得到一个流程分析


关联对象的流程