底层实现原理
- 关联对象的存储:Objective-C 运行时通过一个全局的哈希表来存储关联对象。这个哈希表以对象的内存地址作为 key,每个 key 对应一个
objc_object
结构体,结构体中包含了指向关联对象数组的指针。
- 关联策略:运行时使用关联策略(
objc_AssociationPolicy
)来决定如何管理关联对象的内存。这些策略包括 OBJC_ASSOCIATION_ASSIGN
(弱引用)、OBJC_ASSOCIATION_RETAIN_NONATOMIC
(非原子强引用)、OBJC_ASSOCIATION_COPY_NONATOMIC
(非原子拷贝)、OBJC_ASSOCIATION_RETAIN
(原子强引用)和 OBJC_ASSOCIATION_COPY
(原子拷贝)。
- 关联过程:当使用
objc_setAssociatedObject
函数来设置关联对象时,运行时会根据传入的关联策略,将关联对象存储到全局哈希表中与目标对象对应的位置。在获取关联对象时,通过 objc_getAssociatedObject
函数从哈希表中查找对应的对象。
实际开发场景
- 给分类添加属性:在 Objective-C 中,分类不能直接添加实例变量,但可以通过关联对象来模拟添加属性。例如,给
UIButton
分类添加一个自定义属性 customData
,用于存储与按钮相关的额外数据。
#import <objc/runtime.h>
@interface UIButton (CustomData)
@property (nonatomic, strong) id customData;
@end
@implementation UIButton (CustomData)
static const char customDataKey;
- (void)setCustomData:(id)customData {
objc_setAssociatedObject(self, &customDataKey, customData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)customData {
return objc_getAssociatedObject(self, &customDataKey);
}
@end
- 在不继承的情况下扩展对象功能:比如在一个基础的
NSObject
子类上,通过关联对象添加一些特定功能的数据,而无需创建新的子类。例如,为网络请求类添加一个用于存储请求标识的属性,方便在请求过程中进行管理。
@interface NetworkRequest : NSObject
@end
@implementation NetworkRequest
@end
@interface NetworkRequest (RequestIdentifier)
@property (nonatomic, copy) NSString *requestIdentifier;
@end
@implementation NetworkRequest (RequestIdentifier)
static const char requestIdentifierKey;
- (void)setRequestIdentifier:(NSString *)requestIdentifier {
objc_setAssociatedObject(self, &requestIdentifierKey, requestIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)requestIdentifier {
return objc_getAssociatedObject(self, &requestIdentifierKey);
}
@end
避免内存管理问题
- 选择合适的关联策略:根据关联对象的生命周期需求,选择正确的关联策略。如果关联对象的生命周期与宿主对象不一致,并且不希望宿主对象持有强引用导致循环引用,应选择
OBJC_ASSOCIATION_ASSIGN
。例如,在处理视图控制器与临时数据的关联时,如果临时数据的生命周期可能比视图控制器短,使用弱引用策略。
- 及时释放关联对象:在宿主对象的
dealloc
方法中,手动清除关联对象,以避免内存泄漏。例如:
@interface MyViewController : UIViewController
@end
@implementation MyViewController
static const char associatedObjectKey;
- (void)dealloc {
objc_setAssociatedObject(self, &associatedObjectKey, nil, OBJC_ASSOCIATION_ASSIGN);
}
@end
- 避免循环引用:在使用关联对象时,要注意避免循环引用。如果两个对象相互通过关联对象持有对方,可能会导致对象无法释放。可以通过合理设计对象关系,或者使用弱引用策略来打破循环。例如,在视图控制器与它所管理的子视图之间,如果子视图通过关联对象持有视图控制器,应使用弱引用策略。