文章目录
分类实现原理
之前我们简单学习过分类与扩展的原理,知道了扩展是在编译时就被添加到类中,而分类是运行时才会被整合到类信息中的,这里我们探究一下分类编译后的底层结构
先看源码实现:
objc
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;//只存属性元数据,,主要包含属性名和编码后的 attributes 描述;它让 runtime 知道这个属性怎么声明,但它本身不负责提供实例存储
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) const {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi) const;
protocol_list_t *protocolsForMeta(bool isMeta) const {
if (isMeta) return nullptr;
else return protocols;
}
};
//有方法列表、协议列表、属性列表但是没有ivar列表,就是说分类底层其实根本不会负责扩展实例内存布局
分类的加载流程
- 在编译阶段将分类中的信息整合到一个category_t数据结构中,拿到
category_t - 暂存未附着分类:
addUnattachedCategoryForClass - 附着到类/元类:
methodizeClass或remethodizeClass→attachCategories - 调度
+load:add_category_to_loadable_list→call_load_methods
一句话来说就是:镜像进来之后,runtime先将分类登记到未附着表,等待目标类method/remethodize时,再将分类的方法、属性、协议挂到类/元类上,如果分类实现了load方法,再按照"主类先、分类后"的规则调用
关联对象
首先我们看这个例子:
objc
#import "ZYLPerson+Pop.h"
#import <objc/runtime.h>
static const void* nameKey = &nameKey;
@implementation ZYLPerson (Pop)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self,nameKey , name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, nameKey);
}
- (void)cate_instanceMethod {
NSLog(@"%s", __func__);
}
+ (void)cate_classMethod {
NSLog(@"%s", __func__);
}
@end
上面的例子我们简单实现了关联对象的流程,下面我们看看关联对象的底层。主要是一个双层哈希表结构
runtime是通过两步查找,首先根据对象地址找到对象自己的关联表。
objc
object -> ObjectAssociationMap//即定位是哪个对象的外挂存储区
//键:每个实例的伪装指针,通过位运算将对象的内存地址转换为整形,避免直接暴露指针
//值:指向该实例专属的第二层哈希表objectAssociationMap的指针
然后再在对象对应的关联表中根据key找到具体的关联值
objc
key -> ObjcAssociation//这里的ObjcAssocation不只是value,本质上还带了policy,即一些属性修饰策略
//开发者定义的静态键
//值:封装关联值和内存策略的objcAssociation结构体
源码探索
objc
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if (!object && !value) return;
Class objectClass = object->getIsa();//拿当前类指针
objectClass->realizeIfNeeded();//确保类已经完成runtime层面的realize
if (objectClass->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};//获取对象的伪装指针,第一层hash表使用的key
ObjcAssociation association{policy, value};//包装要存储的值
association.acquireValue();//加锁前完成新值的内存管理操作
bool isFirstAssociation = false;
{
AssociationsManager manager;//一个RALL型的管理器,处理构造加锁、析构解锁
AssociationsHashMap &associations(manager.get());//获取全局关联表
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//如入第一层表已经有了这个对象,取出其对应二级表,如果没有,就新建一个空的二级表
if (refs_result.second) {//判断第几次关联
isFirstAssociation = true;
}
auto &refs = refs_result.first->second;//拿到专属第二层表
auto result = refs.try_emplace(key, std::move(association));//按key插入,如果原来不存在就插入,存在就不插入,返回已有项
if (!result.second) {//如果key已经存在就替换旧值
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);//如果第二层表空了,就直接将第一层表也删除了
}
}
}
}
}
//这一步必须在锁外操作,他可能调用_noteAssociatedObjects从而触发对象的+initailize,导致触发其他关联对象操作等
if (isFirstAssociation)
object->setHasAssociatedObjects();
//锁外释放旧值
association.releaseHeldValue();
}
AssociationManager:全局的单例管理器,通过C++的RALL模式管理互斥锁,构造函数加锁,析构函数解锁,保证线程安全
接下来我们看读源码
objc
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//创建一个空的ObjcAssociation,用来存储后续查到的关联项
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
//先查对象的关联表,这里是按照object查
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();//这里会根据条件进行retain操作
}
}
}
//锁只保护共享数据结构本身,不拿着锁做更复杂的对象生命周期交互。
return association.autoreleaseReturnedValue();//返回前根据getter policy决定是否需要autorelease一下
}
- retainReturnedValue:
根据关联策略(如 OBJC_ASSOCIATION_RETAIN)对值执行 retain
特殊处理 COPY 策略:如果值支持 NSCopying 协议,执行 copy 操作
- autoreleaseReturnedValue:
将返回值加入当前自动释放池,延迟释放时机
即使策略是 ASSIGN 也强制 autorelease,避免野指针风险
关联对象不需要我们去管理内存
objc
inline void
objc_object::rootDealloc()
{
// 小对象无需内存收
if (isTaggedPointer()) return; // fixme necessary?
// 快速路径判断:对象满足以下所有条件时可直接释放
if (fastpath(isa.nonpointer && // 使用优化的非指针型isa
!isa.weakly_referenced && // 无弱引用指向该对象
!isa.has_assoc && // 未设置关联对象
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor && // 无C++析构函数,如果有需要先执行析构函数
#else
!isa.getClass(false)->hasCxxDtor() && // 旧版本检查类是否有C++析构
#endif
!isa.has_sidetable_rc)) // 未使用sidetable存储引用计数
{
assert(!sidetable_present());//快速路径
free(this); // 直接释放内存
}
else {
object_dispose((id)this); // 需要复杂处理的场景
}
}
// 对象销毁入口函数,如果对象牵涉weak、关联对象、C++析构、side tbale等复杂机制,就不能走直接free
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj); // 执行实例销毁逻辑,析构对象内部状态
free(obj); // 释放对象内存
return nil;
}
// 对象实例销毁核心逻辑,将对象身上挂着的各种运行时机制清除掉
void *objc_destructInstance(id obj)
{
if (obj) {
// 一次性读取所有标志位以优化性能
bool cxx = obj->hasCxxDtor(); // 检查是否有C++析构函数
bool assoc = obj->hasAssociatedObjects(); // 检查是否有关联对象
// 处理顺序非常重要(先析构再移除关联)
if (cxx) object_cxxDestruct(obj); // 执行C++析构函数
if (assoc) _object_remove_assocations(obj, /*deallocating*/true); // 移除关联对象
obj->clearDeallocating(); // 清理弱引用和sidetable引用计数
}
return obj;
}
