前言
iOS 底层第17天的学习。今天主要分享的就是对 category 一些相关知识点的补充
类扩展分析
- 分析前准备👇
// 类扩展写在申明之前,实现之后
@interface XKStudent ()
@property (nonatomic, copy) NSString *ext_hobby;
@property (nonatomic, assign) int ext_age;
- (void) ext_instanceMethod;
+ (void) ext_classMethod;
@end
@implementation XKStudent
- (void) ext_instanceMethod {
}
+ (void) ext_classMethod {
}
- 准备完毕后,
clang一下 XKStudent.m 编译成 .cpp 文件
clang -rewrite-objc XKStudent.m -o XKStudent.cpp
- 打开
XKStudent.cpp文件,搜索ext_instanceMethod

- 👆 得知
类的扩展的实例方法会在编译时存在于method_list_t,但我们还不能确定真的如编译器显示的那样,会直接加载到method_list_t。 - 接下来我们继续验证 , 调用
load进行非懒加载, 验证类扩展是否和category一样
@implementation XKStudent
+ (void) load {}
- (void) ext_instanceMethod {
}
+ (void) ext_classMethod {
}
- (void)teacherSay{}
@end
- 进入
realizeClassWithoutSwift输入出ro👇

小结
- 可知在进行
非懒加载,类扩展里的实例方法会在编译时存在于method_list,这一点是与category不同的。 -
extension 类扩展- 特殊的分类
- 可以给类添加成员变量,但都是私有的
- 可以给类添加方法,但都是私有的
-
category 类别,分类- 专门用来给类添加新的方法
- 不能用常规的方法添加成员属性,成员变量,也无法取到,但可以
通过 runtime 给分类添加属性 - 分类中用
@property定义变量,只会生成getter,setter方法的声明,不能生成方法实现和带下划线的成员变量
category 关联对象分析
- 当我们不能用常规的方法给
category添加成员属性,成员变量,也无法取到,但可以通过 runtime api 给分类添加属性
而这个
api - objc_setAssociatedObject,那objc_setAssociatedObject是如何给category添加成员属性的?
objc_setAssociatedObject
- 分析前准备👇
#import "XKStudent+XKA.h"
#import "objc/runtime.h"
@implementation XKStudent (XKA)
- (void)setCname:(NSString *)cname {
objc_setAssociatedObject(self, "c_name", cname,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *) cname {
return objc_getAssociatedObject(self,"c_name");
}
- 进入
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
// 核心方法
// id object: 对象 eg:XKStudent
// const void *key: 键值
// id value: eg: 存储的数据 eg:小 name = 小看emiya
// uintptr_t policy: 缓存测量 retain, copy
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));
// 包装成同一的数据结构 disguised = 伪装;假装
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 对象的存储
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
// ... 核心逻辑
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
先静态全局分析一下
-DisguisedPtr<objc_object> disguised{(objc_object *)object}包装伪装成同一的数据结构
-ObjcAssociation association{policy, value}对象的存储 ,policy= 缓存策略 ,value= 值
-bool isFirstAssociation = false; { ... }开始插入表操作开始动态分析

- 输出👇

- 可知
association的确存储了value和policy,disguised包装了object - 继续往下
step

- 进入 ·
AssociationsManager

-
mapStorage静态变量,可知ObjectAssociationMap是唯一的表 - 继续
step

- 输出一下
associations

- 当进行了
associations.try_emplace(disguised, ObjectAssociationMap{});,再次输出
associations

- 可知
associations里的buckets已经有了地址了。 - 进入
associations.try_emplace

- 这时是第一次进入
LookupBucketFor,没有找到bucket,继续往下step

- 输出
refs_result

- 继续
step

- 输出
refs👆 得知refs从refs_result.first->second读取到的

- 里面的
Buckets = nil - 继续
step👇

- 输出
result查看返回结果


可知再进行
try_emplace(key, std::move(association))这步操作时,把association存储到了
ObjcAssociationMap, 之后再把ObjcAssociationMap插入到buckets继续
step,设置value = nil来到else👇

- 可知当
value = nil找到当前disguised_key所绑定的refs_it, 根据key找到it, 最后再erase.
总结
- 看一下图,或许能更容易理解
objc_setAssociatedObject底层实现,图有点丑多多包涵!

-
AssociationsHashMap是一张全局的总表,一层一层的嵌套,以键值的形式存储数据👇
