一. 类拓展
类拓展的定义可以详细参考苹果官方文档
A class extension bears some similarity to a category, but it can only be added to a class for which you have the source code at compile time (the class is compiled at the same time as the class extension).
类扩展与类别具有某些相似性,但只能将其添加到编译时具有源代码的类(该类与类扩展同时编译)。类扩展也被称为匿名的分类。
类扩展在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,extension伴随类的产生而产生,亦随之一起消亡。
类拓展有两种形式
- 直接在.m文件中新增类拓展
- 新建extension.h文件
1.1 类拓展的加载
根据官方文档,类拓展是编译时与类同时加载的。
我们知道,类加载时数据很早的时候都会来到_read_image,我们可以在_read_image中,查看AKPerson的ro信息,如果有extension_Method方法,说明类拓展是编译时加载的。
查看属性的setter和getter方法来进一步验证
结论:
- 类拓展在编译时便作为类的一部分进行编译
- 类拓展在读取时直接读取ro
注意:
如果我们没有在类的头文件或者源文件中引入单独的类拓展头文件,那么这个单独的类拓展的头文件里面的属性和方法将不会被加载到类上面来。
1.2 类拓展和分类的区别
| 研究对象 | 加载时机 | 操作对象 | 能否通过@property声明属性生成 getter 和 setter |
|---|---|---|---|
| 非懒加载分类 | 运行时 | rw | 不能,需要借助关联对象来实现 |
| 懒加载分类 | 编译时 | ro | 不能,需要借助关联对象来实现 |
| 类拓展 | 编译时 | ro | 可以 |
二. 关联对象
类拓展中可以声明属性,编译器会帮助我们生成属性对应的 getter 和 setter 方法,但是分类通过 @property 的方式来声明属性却不能生成 getter 和 setter 方法,这时候就需要使用关联对象 Associated Objects 。
2.1 关联对象定义
关联对象的定义可以详细参考苹果官方文档
Associative references, available starting in OS X v10.6, simulate the addition of object instance variables to an existing class. Using associative references, you can add storage to an object without modifying the class declaration. This may be useful if you do not have access to the source code for the class, or if for binary-compatibility reasons you cannot alter the layout of the object.
关联引用,是从 OS X 10.6 开始启用的,模拟了将对象实例变量添加到已经存在的类中。通过使用关联引用,你可以在不修改类声明的前提下为对象添加内容。如果你无权访问该类的源代码,或者由于二进制兼容性原因而无法更改该对象的布局,则这可能很有用。
Associations are based on a key. For any object you can add as many associations as you want, each using a different key. An association can also ensure that the associated object remains valid for at least the lifetime of the source object.
关联引用机制基于 key。对于任何对象,你都可以根据需要添加任意数量的关联引用,每个关联都使用不同的 key。关联引用还可以确保关联的对象至少在源对象的声明周期内保持有效。 而关于关联对象的最佳实践可以参考 NSHipster - Associated Objects 一文。
从苹果官方文档可以看到,关联引用其实不是只能在分类中使用,只不过对于我们日常开发来说,分类中使用关联引用还是更常用的场景。
2.2 objc_setAssociatedObject - 以键值对形式添加关联对象
**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
解释下参数:
- 参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
- 参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
- 参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
- 参数四:objc_AssociationPolicy policy : 关联策略,属性以什么形式保存。
| 关联策略 | 等同的@Property | 描述 |
|---|---|---|
| BJC_ASSOCIATION_ASSIGN = 0 | @property (assign) | 指定一个关联对象的弱引用 |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, copy) | 指定一个关联对象的强引用,不能被原子化使用。 |
| OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 指定一个关联对象的copy引用,不能被原子化使用 |
| OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 指定一个关联对象的强引用,能被原子化使用 |
| OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 指定一个关联对象的copy引用 ,能被原子化使用 |
继续跟进源码
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
objc_setAssociatedObject 方法的实现又包裹了一层,其实现为 _object_set_associative_reference(苹果的经常将带下划线的底层方法包裹一层,防止开发人员调用私有API)
继续跟进源码,来到核心代码
_object_set_associative_reference
阅读_object_set_associative_reference流程前,我们先学习下objc_setAssociatedObject的四个核心对象
- AssociationsManager 管理者
- AssociationsHashMap 所有对象的关联表
- ObjcAssociationMap 当前对象包含的所有关联对象
- ObjcAssociation 关联存储
1. AssociationsManager 管理者
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsManager维护了spinlock_t和AssociationsHashMap的单例,- 初始化的时候会调用
lock.lock()方法, - 析构时会调用
lock.unlock()方法 associations方法用于取得一个全局的AssociationsHashMap单例。
AssociationsManager通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作
2. AssociationsHashMap 所有对象的的关联表
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
AssociationsHashMap 用与保存从对象的disguised_ptr_t 到 ObjectAssociationMap 的映射。可以理解为所有对象的关联表。
3. ObjcAssociationMap 当前对象包含的所有关联对象
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
ObjectAssociationMap 保存了从 key 到关联对象 ObjcAssociation 的映射。可以理解为当前对象包含的所有关联对象。
4. ObjcAssociation(policy, new_value) 关联存储
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
ObjcAssociation 包含了value关联的值和policy关联策略 :
例如:ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello")按照下图的层次存储
5. _object_set_associative_reference流程
了解几个基础知识,进入void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)源码。
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
...
// retain the new value (if any) outside the lock.
// 在锁之外保留新值(如果有)。
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;id new_value = value ? acquireValue(value, policy) : nil;
....
}
- 使用
old_association(0, nil)创建一个临时的ObjcAssociation对象(用于持有原有的关联对象,方便在方法调用的最后释放值) - 将传入的参数
value赋值给new_value, 根据new_value是否有值做两种操作:
new_value有值 -- 更新/新增关联对象的值。acquireValue会根据policy关联策略对new_value进行retain或者copy
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
new_value无值 -- 删除一个关联对象
new_value != nil
如果nnew_value != nil 说明我们要更新或者新增关联对象的值
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 在锁之外保留新值(如果有)。用于持有原有的关联对象,方便在方法调用的最后释放值。
ObjcAssociation old_association(0, nil);
id new_value = acquireValue(value, policy);
// 1. 关联对象的管理类
AssociationsManager manager;
// 2. 获取所有关联对象的哈希表 `AssociationsHashMap`
AssociationsHashMap &associations(manager.associations());
// 3.对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数)
// 3.1 生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
// 3.2 以`DISGUISE(object)`作为key 寻找对应的 `AssociationsHashMap`
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
//4. 以 `void *key` 为 key 查找 `ObjcAssociation`
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
// 替换设置新值
j->second = ObjcAssociation(policy, new_value);
} else {
// 到最后了 - 直接设置新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// 如果AssociationsHashMap从没有对象的关联信息表,
// 那么就创建一个map并通过传入的key把value存进去
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
}
// release the old value (outside of the lock).
// 5. 最后如果原来的关联对象有值的话,调用 `ReleaseValue()` 释放关联对象的值`old_association`
if (old_association.hasValue()) ReleaseValue()(old_association);
}
- 初始化
AssociationsManager - 获取所有关联对象的哈希表
AssociationsHashMap - 以
DISGUISE(object)作为 key 寻找对应的AssociationsHashMap - 以
void *key作为 key 寻找对应的ObjectAssociationMap
- 找到了:调用
ObjcAssociation(policy, new_value):如果key已经存在,更新关联策略和值;key不存在,增加一个。 - 没有找到:初始化一个
ObjectAssociationMap,再实例化ObjcAssociation对象添加到 Map 中,并调用setHasAssociatedObjects方法,表明当前对象含有关联对象
- 最后,如果原来的关联对象有值的话,调用
ReleaseValue()释放关联对象的值
new_value == nil
如果 new_value == nil,就说明我们要删除对应 key 的关联对象,
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 在锁之外保留新值(如果有)。
ObjcAssociation old_association(0, nil);
id new_value = acquireValue(value, policy);
// 1. 关联对象的管理类
AssociationsManager manager;
// 2. 获取关联的 HashMap -> 存储当前关联对象
AssociationsHashMap &associations(manager.associations());
// 3.对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数)
// 3.1 生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// setting the association to nil breaks the association.
// 如果传入的value是nil,并且之前使用相同的key存储过关联对象,
// 那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
// release the old value (outside of the lock).
// 4. 最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)
if (old_association.hasValue()) ReleaseValue()(old_association);
}
调用 erase 方法,删除 ObjectAssociationMap 中 key 对应的值。
2.3 objc_getAssociatedObject - 根据 key 获取关联对象
函数调用栈与Objc_setAssociatedObject基本一致:objc_getAssociatedObject -> _object_get_associative_reference -> _object_get_associative_reference
解释下参数:
- 参数一:id object : 获取哪个对象里面的关联的属性。
- 参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 1. 关联对象的管理类
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 2. 生成伪装地址。处理参数 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// 所有对象的额迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 内部对象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到 - 把值和策略读取出来
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// OBJC_ASSOCIATION_GETTER_RETAIN - 就会持有一下
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
- 获取
AssociationsHashMap - 以 DISGUISE(object) 为 key 查找 AssociationsHashMap
- 以 void *key 为 key 查找 ObjcAssociation
- 根据 policy 调用相应的方法。返回关联对象 ObjcAssociation 的值
2.4 objc_removeAssociatedObjects - 移除所有关联对象
函数调用栈:objc_removeAssociatedObjects —> _object_remove_assocations
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
将对象包含的所有关联对象加入到一个 vector 中,然后对所有的 ObjcAssociation 对象调用 ReleaseValue() 方法,释放不再被需要的值。
2.5 总结
由于 category_t 的结构体里没有 objc_ivar_list,所以分类无法添加成员变量,可以通过 @property 添加属性,但是分类中的 @property不能自动生成 setter getter 方法,需要我们关联对象手动生成。
关联对象涉及数据结构:
AssociationManager:
- 维护了自旋锁
spinlock_t和AssociationHashMap单例。 spinlock_t保证对AssociationManager的操作是线程安全的,每次只有一个线程来操作。
AssociationHashMap:
- key可以理解为
object的指针(通过DISGUISE()包装) - value是
ObjcAssociationMap表的首地址
ObjcAssociationMap
- key 是
const void * _Nonnull key,即@selector(associationObject) - value 是
ObjcAssociation对象
ObjcAssociation
- 关联对象其实就是
ObjcAssociation - 内部存储了
id _Nullable valueobjc_AssociationPolicy policy