在iOS开发中,我们经常使用到category,使用category可以给类添加方法或者属性,在添加属性的时候,如果仅仅声明属性,编译器并不会自动生成set和get方法,此时直接使用属性进行读写会发生crash,此时需要通过关联对象来增加属性的set和get方法实现。
关联对象的使用
先创建一个类名称为Custom,并且增加category命名为Prop,写入代码
Custom.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Custom : NSObject
@property(nonatomic, strong) NSString *string;
@end
NS_ASSUME_NONNULL_END
Custom.m
#import "Custom.h"
@implementation Custom
@end
Custom+Prop.h
#import "Custom.h"
NS_ASSUME_NONNULL_BEGIN
@interface Custom (Prop)
@property(nonatomic, strong) NSString *str;
@end
NS_ASSUME_NONNULL_END
Custom+Prop.m
使用关联对象需要加<objc/runtime.h>头文件
objc_setAssociatedObject和objc_getAssociatedObject的第二个参数可以是@selector(propertyName),propertyName为属性名称
OBJC_ASSOCIATION_RETAIN_NONATOMIC是policy参数的取值
#import "Custom+Prop.h"
#import <objc/runtime.h>
@implementation Custom (Prop)
- (void)setStr:(NSString *)str
{
objc_setAssociatedObject(self, @selector(str), str, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)str
{
return objc_getAssociatedObject(self, @selector(str));
}
@end
代码完成后可以使用Custom的属性str进行读写了。
category为什么可以添加属性和方法
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
从category的定义中可以看出,category持有了实例方法列表,类方法列表,协议列表,实例属性列表,类属性列表。因此,category可以给类添加相应的实例方法,类方法,协议,实例属性,类属性。
关联对象实现原理
首先了解下关联对象的数据结构
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
这里是AssociationsManager,所有对象的所有关联对象都保存在这里。
在app的启动阶段,dyld链接动态库的时候,就会调用init初始化函数用来初始化_mapStorage
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
这里的_mapStorage是Map的数据结构,key为DisguisedPtr类型,也就是对象封装后的类型,value是ObjectAssociationMap类型,
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
这里的ObjectAssociationMap也是Map的数据结构,这个Map的key是关联对象的key,也就是objc_getAssociatedObject(id object, const void *key)接口中参数key,ObjectAssociationMap中的value就是关联对象ObjcAssociation。
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
这个是关联对象的部分定义,ObjcAssociation持有了_policy和对应的_value,_value是关联对象的实际取值。
关联对象的数据结构可以总结为:AssociationsManager负责初始化Map类型的_mapStorage,_mapStorage保存了有关联对象的所有实例,一个实例的DisguisedPtr为一个key。_mapStorage的value为当前实例的关联对象,由于关联对象可以有多个,_mapStorage持有了ObjectAssociationMap管理这些关联对象。ObjectAssociationMap的key为代码中设置的key,value为关联对象ObjcAssociation。实际上,关联对象是用两层Map来实现的。
继续使用Custom中的例子,我们对str属性赋值的时候,此时会调用到对应的set方法。
void objc_setAssociatedObject(id oject, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
set方法会调用到_object_set_associative_reference这个方法
oid
_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};
association.acquireValue();
//判断是否是首次设置关联对象
bool isFirstAssociation = false;
{
AssociationsManager manager;
//获取AssociationsHashMap
AssociationsHashMap &associations(manager.get());
//value不为空
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* 新建或者替换 association */
auto &refs = refs_result.first->second;
//map中增加key和association
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
//删除association
refs.erase(it);
//如果当前实例没有关联对象,AssociationsHashMap中需要删除这个对象
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
//判断是否是首次设置关联对象,如果是,需要设置实例的标记位
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
在设置关联对象的时候,需要获取到AssociationsHashMap,然后找到对象的ObjectAssociationMap,把ObjcAssociation插入。这里需要注意的是如果value为nil,也就是给关联对象赋值为nil,需要把ObjcAssociation删除,如果当前对象已经没有关联对象,需要把当前对象从AssociationsHashMap中删除。
最后需要释放之前持有的对象。
association.releaseHeldValue();
inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
objc_release(_value);
}
}
只有强持有的对象才需要release。
在取用关联对象的时候,会调用objc_getAssociatedObject方法
id objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
get方法相对简单些,获取AssociationsHashMap然后获取AssociationsHashMap,查找对应的关联对象值返回。
关联对象的释放
当一个实例对象销毁的时候,dealloc方法内部会判断当前对象有没有关联对象,如果有就会调用_object_remove_associations方法
void
_object_remove_associations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
//移除关联对象
if (!didReInsert)
associations.erase(i);
}
}
SmallVector<ObjcAssociation *, 4> laterRefs;
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
找出对应的ObjectAssociationMap,从associations中移除相应的对象。