前言
研究完了分类的方法,那么还有一个重要的点就是分类对于属性的动态加载,如果只是写了属性,没有实现set、get方法系统是会报警告的,那么底层对于分类的属性做了什么处理呢?本节主要梳理分类的属性加载原理,另外关于类扩展的基本知识点补充。
分类的属性
正常我们往分类里添加属性,需要实现set、get方法。
-(**void**)setCateA_name:(NSString *)cateA_name
{
objc_setAssociatedObject(**self**, @"cateA_name", cateA_name, OBJC_ASSOCIATION_RETAIN);
}
-(NSString *)cateA_name
{
**return** objc_getAssociatedObject(**self**,@"cateA_name");
}
关于objc_setAssociatedObject
的定义,每个版本都不一样,这里属于中间层,为的就是保证上层的代码不会变,下层的可以扩展。
objc_setAssociatedObject(**id** object, **const** **void** *key, **id** value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
那么重点的代码就来到了实现的方法。从左往右的参数依次代表了被关联者
、key
、value
、关联策略
。被关联者就是当前关联的是哪个对象,关联策略根据关联数据有不同的类型,比如assign、copy等等。DisguisedPtr<objc_object> disguised{(objc_object *)object};
把所有的关联对象统一封装成ptr的形式,统一数据结构。
_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));
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**;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
**if** (value) {
**auto** refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
**if** (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = **true**;
}
/* establish or replace the association */
**auto** &refs = refs_result.first->second;
**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);
refs.erase(it);
**if** (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// 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();
}
上面的AssociationsHashMap这是一张全局的表,存储了所有的对象关联的另一张hash表objectAssociateionMap,这张表里左边的const void*表示的对应的对象的属性,所对应的封装好的值的类型,结构就是objectAssociation,也就是ObjcAssociation association{policy, value};
这个方法所做的封装结构。
AssociationsManager manager;
--- 这是一个构造函数。
AssociationsManager()函数开锁,~AssociationsManager()上锁,表示只存在该作用域的空间,也就是 AssociationsManager manager;
所在方法的作用域空间。static Storage _mapStorage;
属于全局静态变量,并且只能通过AssociationsManager声明调用。
**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();
}
};
总体流程
根据对象找到关于这个对象的hash表,里面可能会有很多的bucket,第一次进来就会判断有没有bucket,如果没有bucket就会插入一个,bucket里面有很多关于这个类的信息(属性...),bucket的扩容同之前的方法扩容一样,然后去找是关于哪个关联对象的值,如果传值传空,就会调erase,清空。
关联对象的释放
生命周期随着objcet的释放而释放,对外方法objc_removeAssociatedObjects,对内方法_object_remove_assocations
,在dealloc
里面.
类扩展补充
在我们平时使用中非常的多,说的简单一点就是一个匿名的分类,他是写在声明后,实现前。写一个类扩展并且xcrun看一下编译情况,并没有发现类扩展有单独的结构体,那么类扩展有自己的方法处理呢?通过在非懒加载模式下在分类方法插入之前直接打印ro,发现已经把方法加载进去了,说明类扩展的方法是伴随着类一起加载进去的,即使你单独写一个类扩展的声明,在主类中实现,也是一样的。