前言
前面的文章类的加载原理(下)中,我们分析了分类的加载,那么分类是怎么关联对象的呢?类扩展是什么?他们有什么区别吗?本文将针对这些问题去探究下。
类扩展
类扩展和分类的区别
category:分类、类别-
- 专门给类
添加新的方法
- 专门给类
-
不能给类添加成员属性,添加了成员属性也无法取到
-
可以通过runtime给分类添加属性,需要重写setter和getter方法
-
- 分类中用
@property定义变量,只会生成 变量的setter、getter方法的声明,不能生成方法实现和带下划线的成员变量。
- 分类中用
-
extension:类扩展-
- 可以说成是
特殊的分类,也可称作匿名分类
- 可以说成是
-
- 可以
给类添加成员属性,但是是私有变量
- 可以
-
- 可以
给类添加方法,也是私有方法
- 可以
-
类扩展的底层实现
在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:
- 发现
扩展的方法,都在method_list_t,并没有生成新的结构。那么扩展的方法会直接加到method_list_t吗?接下来在objc4-812中去调试,发现会直接加进去。
分类关联对象
我们知道,分类在添加对象时,需要使用runtime实现setter, getter方法,那么这个过程是怎样的?在源码中去调试探究下。
- 在
WSPerson分类中定义两个属性,然后实现setter和getter方法: - 关联类用到了
objc_setAssociatedObject和objc_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;
}
}
}
核心代码
- 核心代码中先创建了
AssociationsManager对象manager,再获取全局的hashMap表,然后根据value进行处理
析构函数
AssociationsManager的源码如下:
- 其中
AssociationsManager()是构造函数 ~AssociationsManager()是析构函数
析构函数:
析构函数是特殊的类成员函数,简单来说析构函数与构造函数的作用正好相反,它用来完成对象被删除前的一些清理工作,也就是专门的扫尾工作。
代码验证
- 下来定义一个简单的结构体,然后里面有个
构造方法和析构方法:
struct Sport {
Sport() { NSLog(@"我是一只酸菜鱼~"); }
~Sport() { NSLog(@"我是一只黄焖鸡~"); }
};
- 然后再
main中调用,当走完构造方法:打印如下:
- 出作用域后,再打印:
- 说明析构函数是在出作用域后调用的,再来看看
AssociationsManager,它的构造函数是加锁,它的析构函数是解锁。
AssociationsHashMap
AssociationsHashMap &associations(manager.get())是通过manager.get()获取HashMap,它的源码是通过_mapStorage调用get()方法实现的,而_mapStorage是一个static类型,所以获取的这张表是唯一的,这个获取表的过程是个单例:
- 接下来走到判断,如果
value有值时:associations调用了try_emplace创建了个对象refs_result,先来打印下这个类型,断点至此处在运行:
- 得到
refs_result是一个比较长的类型,看着比较吓人,但实质只需要用second参数,再分析下try_emplace函数
第一次try_emplace
它的实现如下:
-
- 先来看看
LookupBucketFor的实现,发现有两个同名函数:
- 先来看看
由于传入的类型不是const,断点走的是下面一个方法,但最中间代码还是走到了上面的LookupBucketFor,它的核心实现我们很熟悉:
和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来执行插入操作,源码如下:
这个过程我们也比较熟悉,就是插入时容积的处理,如果容积大于等于3/4,需要2倍扩容。
- 获取到
TheBucket后,就对TheBucket,的first和second进行相关赋值:
TheBucket的first就是object强转的DisguisedPtr<objc_object>类型second就是ObjectAssociationMap表
第二次try_emplace
第二次调用时,传入的参数不一样了,也就是TheBucket的参数变了
TheBucket的first就是const void *类型的keysecond就是ObjcAssociation类型,而ObjcAssociation存储的是policy和value
erase
- 当
value值不存在时,会调用erase方法清除bucket,和ObjcAssociation
流程图
objc_getAssociatedObject
- 先来看看源码:
分析
- 里面主要是调用的
_object_get_associative_reference方法,他的实现如下:
- 其中
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