iOS底层-分类的关联对象和类扩展

752 阅读5分钟

前言

前面的文章类的加载原理(下)中,我们分析了分类的加载,那么分类是怎么关联对象的呢?类扩展是什么?他们有什么区别吗?本文将针对这些问题去探究下。

类扩展

类扩展和分类的区别

  • category:分类、类别
      1. 专门给类添加新的方法
      1. 不能给类添加成员属性,添加了成员属性也无法取到
      1. 可以通过runtime给分类添加属性,需要重写settergetter方法
      1. 分类中用@property定义变量,只会生成 变量的setter、getter方法的声明不能生成方法实现带下划线的成员变量
  • extension:类扩展
      1. 可以说成是特殊的分类,也可称作匿名分类
      1. 可以给类添加成员属性,但是是私有变量
      1. 可以给类添加方法,也是私有方法

类扩展的底层实现

main中定义一个WSAnimal类和他的扩展:

// .h
@interface WSAnimal : NSObject

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

- (void)instance_method;
+ (void)class_method;

@end

// 扩展
@interface WSAnimal ()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int    ext_age;

- (void)ext_instance_method;
+ (void)ext_class_method;

@end

// .m
@implementation WSAnimal

- (void)instance_method {
    NSLog(@"%s", __func__);
}
+ (void)class_method {
    NSLog(@"%s", __func__);
}
- (void)ext_instance_method {
    NSLog(@"%s", __func__);
}
+ (void)ext_class_method {
    NSLog(@"%s", __func__);
}

@end
  • clang生成main.cpp,然后搜索ext_instance_method

截屏2021-07-25 08.56.43.png

  • 发现扩展的方法,都在method_list_t,并没有生成新的结构。那么扩展的方法会直接加到method_list_t吗?接下来在objc4-812中去调试,发现会直接加进去。

分类关联对象

我们知道,分类在添加对象时,需要使用runtime实现setter, getter方法,那么这个过程是怎样的?在源码中去调试探究下。

  • WSPerson分类中定义两个属性,然后实现setter和getter方法: 截屏2021-07-25 13.11.54.png
  • 关联类用到了objc_setAssociatedObjectobjc_getAssociatedObject,我们再来分析下他们的作用

objc_setAssociatedObject

  • objc_setAssociatedObject需要传入4个参数,分别是被关联者关联标记关联对象的值关联策略。 先来看下他的底层实现:
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,再来分析下它主要做了什么

DisguisedPtr<objc_object>

DisguisedPtr<objc_object> disguised{(objc_object *)object};
  • 这是将object强转成objc_object类型,得到DisguisedPtr<objc_object>类型的对象disguised,代码等价于 DisguisedPtr<objc_object> disguised = (objc_object *)object

ObjcAssociation

ObjcAssociation association{policy, value};

主要是是传入policy和value初始化了一个ObjcAssociation类型的对象,然后根据policy类型,对value进行copy或者retain赋值给_value

association.acquireValue();

inline void acquireValue() {
    if (_value) {
        switch (_policy & 0xFF) {
        case OBJC_ASSOCIATION_SETTER_RETAIN:
            _value = objc_retain(_value);
            break;
        case OBJC_ASSOCIATION_SETTER_COPY:
            _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
            break;
        }
    }
}

核心代码

截屏2021-07-25 15.28.03.png

  • 核心代码中先创建了AssociationsManager对象manager,再获取全局的hashMap表,然后根据value进行处理
析构函数

AssociationsManager的源码如下:

截屏2021-07-25 15.29.58.png

  • 其中AssociationsManager()是构造函数
  • ~AssociationsManager()是析构函数

析构函数:析构函数是特殊的类成员函数,简单来说析构函数与构造函数的作用正好相反,它用来完成对象被删除前的一些清理工作,也就是专门的扫尾工作

代码验证
  • 下来定义一个简单的结构体,然后里面有个构造方法析构方法
struct Sport {
    Sport() { NSLog(@"我是一只酸菜鱼~"); }
    ~Sport() { NSLog(@"我是一只黄焖鸡~"); }
};
  • 然后再main中调用,当走完构造方法:打印如下:

截屏2021-07-25 15.42.36.png

  • 出作用域后,再打印:

截屏2021-07-25 15.42.51.png

  • 说明析构函数是在出作用域后调用的,再来看看AssociationsManager,它的构造函数是加锁,它的析构函数是解锁
AssociationsHashMap

AssociationsHashMap &associations(manager.get())是通过manager.get()获取HashMap,它的源码是通过_mapStorage调用get()方法实现的,而_mapStorage是一个static类型,所以获取的这张表是唯一的,这个获取表的过程是个单例

截屏2021-07-25 15.49.40.png

  • 接下来走到判断,如果value有值时:associations调用了try_emplace创建了个对象refs_result,先来打印下这个类型,断点至此处在运行:

截屏2021-07-25 15.57.04.png

  • 得到refs_result是一个比较长的类型,看着比较吓人,但实质只需要用second参数,再分析下try_emplace函数
第一次try_emplace

它的实现如下:

截屏2021-07-25 17.25.50.png

    1. 先来看看LookupBucketFor的实现,发现有两个同名函数:

截屏2021-07-25 17.27.42.png
由于传入的类型不是const,断点走的是下面一个方法,但最中间代码还是走到了上面的LookupBucketFor,它的核心实现我们很熟悉:

截屏2021-07-25 21.22.14.png
cache中的找bucket流程一样

  • 2. InsertIntoBucket:当没有找到时,就会调用插入一个新的bucket,该方法实现如下:
template <typename KeyArg, typename... ValueArgs>
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
      TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
      
      TheBucket->getFirst() = std::forward<KeyArg>(Key);
      ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
      return TheBucket;
  }

主要是调用InsertIntoBucketImpl来执行插入操作,源码如下:

截屏2021-07-25 21.29.50.png
这个过程我们也比较熟悉,就是插入时容积的处理,如果容积大于等于3/4,需要2倍扩容

  • 获取到TheBucket后,就对TheBucket,的firstsecond进行相关赋值: 截屏2021-07-25 22.03.20.png
    • TheBucketfirst就是object强转的DisguisedPtr<objc_object>类型
    • second就是ObjectAssociationMap
第二次try_emplace

第二次调用时,传入的参数不一样了,也就是TheBucket的参数变了

  • TheBucketfirst就是const void *类型的key
  • second就是ObjcAssociation类型,而ObjcAssociation存储的是policyvalue
erase
  • value值不存在时,会调用erase方法清除bucket,和ObjcAssociation

流程图

截屏2021-07-25 22.57.16.png

objc_getAssociatedObject

  • 先来看看源码: 截屏2021-07-25 23.02.19.png

分析

  • 里面主要是调用的_object_get_associative_reference方法,他的实现如下:

截屏2021-07-25 23.26.34.png

  • 其中find函数是寻找bucket过程:
iterator find(const_arg_type_t<KeyT> Val) {
    BucketT *TheBucket;
    if (LookupBucketFor(Val, TheBucket))
        return makeIterator(TheBucket, getBucketsEnd(), true);
    return end();
}
  • 整个流程如下:先获取总HashMap -> 然后根据object获取ObjectAssociationMap bucket -> 再获取ObjectAssociationMap -> 然后获取ObjcAssociation bucket -> 再获取ObjcAssociation -> 最后根据policy返回value

流程图

截屏2021-07-25 23.52.56.png