在 Objective-C 中,当需要向系统类临时添加数据存储时,有几种可靠的技术方案。以下是完整的实现方法和最佳实践:
解决方案概览
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
是否有setValue:forKey方法 | 若有,首选! | ... | ... |
关联对象 | 任意对象的临时属性 | 无需子类化,使用简单 | 需要管理内存策略 |
子类化 | 需要多次复用的属性 | 类型安全,代码清晰 | 需要管理新类 |
分类+静态字典 | 轻量级全局状态 | 简单快速 | 非线程安全 |
代理模式 | 复杂数据存储 | 解耦设计 | 需要额外实现 |
1. 关联对象 (Associated Objects) - 最推荐方案
#import <objc/runtime.h>
// 定义关联键
static char kTempDataKey;
// 设置关联值
- (void)setTempData:(id)tempData {
objc_setAssociatedObject(self,
&kTempDataKey,
tempData,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 获取关联值
- (id)tempData {
return objc_getAssociatedObject(self, &kTempDataKey);
}
// 移除关联值
- (void)clearTempData {
objc_setAssociatedObject(self,
&kTempDataKey,
nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
内存策略选项
策略 | 描述 | 适用场景 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN |
弱引用 | 避免循环引用 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC |
强引用,非原子 | 常用 |
OBJC_ASSOCIATION_COPY_NONATOMIC |
拷贝,非原子 | 字符串/容器 |
OBJC_ASSOCIATION_RETAIN |
强引用,原子 | 多线程 |
OBJC_ASSOCIATION_COPY |
拷贝,原子 | 多线程安全 |
使用示例:为 UIView 添加临时标签
// UIView+TempStorage.h
@interface UIView (TempStorage)
@property (nonatomic, strong) NSString *temporaryTag;
@end
// UIView+TempStorage.m
#import <objc/runtime.h>
@implementation UIView (TempStorage)
static char kTemporaryTagKey;
- (void)setTemporaryTag:(NSString *)temporaryTag {
objc_setAssociatedObject(self,
&kTemporaryTagKey,
temporaryTag,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)temporaryTag {
return objc_getAssociatedObject(self, &kTemporaryTagKey);
}
@end
// 使用
UIView *view = [[UIView alloc] init];
view.temporaryTag = @"special_view";
NSLog(@"Tag: %@", view.temporaryTag);
2. 子类化 (Subclassing)
// 自定义系统类的子类
@interface CustomViewController : UIViewController
@property (nonatomic, strong) id customData;
@end
@implementation CustomViewController
// 正常使用
@end
// 使用
CustomViewController *vc = [[CustomViewController alloc] init];
vc.customData = @{@"key": @"value"};
3. 分类 + 静态字典 (Category + Static Dictionary)
// NSObject+TempStorage.h
@interface NSObject (TempStorage)
- (void)setTempValue:(id)value forKey:(NSString *)key;
- (id)tempValueForKey:(NSString *)key;
@end
// NSObject+TempStorage.m
#import "NSObject+TempStorage.h"
#import <objc/runtime.h>
static NSMutableDictionary *tempStorageDictionary;
@implementation NSObject (TempStorage)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tempStorageDictionary = [NSMutableDictionary new];
});
}
- (void)setTempValue:(id)value forKey:(NSString *)key {
NSString *objectKey = [NSString stringWithFormat:@"%p_%@", self, key];
tempStorageDictionary[objectKey] = value;
}
- (id)tempValueForKey:(NSString *)key {
NSString *objectKey = [NSString stringWithFormat:@"%p_%@", self, key];
return tempStorageDictionary[objectKey];
}
- (void)clearTempValueForKey:(NSString *)key {
NSString *objectKey = [NSString stringWithFormat:@"%p_%@", self, key];
[tempStorageDictionary removeObjectForKey:objectKey];
}
@end
4. 代理模式 (Delegate Pattern)
@protocol DataHolderDelegate <NSObject>
- (void)storeTemporaryData:(id)data;
- (id)retrieveTemporaryData;
@end
// 实现类
@interface DataHolder : NSObject <DataHolderDelegate>
@property (nonatomic, strong) NSMutableDictionary *storage;
@end
@implementation DataHolder
- (instancetype)init {
if (self = [super init]) {
_storage = [NSMutableDictionary new];
}
return self;
}
- (void)storeTemporaryData:(id)data {
NSString *key = [NSString stringWithFormat:@"%p", self];
_storage[key] = data;
}
- (id)retrieveTemporaryData {
NSString *key = [NSString stringWithFormat:@"%p", self];
return _storage[key];
}
@end
// 在系统类中使用
UIViewController *vc = [UIViewController new];
DataHolder *holder = [DataHolder new];
vc.delegate = holder; // 假设有delegate属性
[vc.delegate storeTemporaryData:@"test"];
最佳实践指南
1. 关联对象使用建议
- 键定义:使用静态变量地址作为键,避免冲突
-
命名规范:添加前缀防止冲突
myapp_tempData
-
内存管理:
// 正确释放 - (void)dealloc { objc_removeAssociatedObjects(self); }
2. 线程安全考虑
// 线程安全的关联对象访问
- (void)setThreadSafeData:(id)data {
@synchronized(self) {
objc_setAssociatedObject(self,
&kThreadSafeKey,
data,
OBJC_ASSOCIATION_RETAIN);
}
}
- (id)threadSafeData {
@synchronized(self) {
return objc_getAssociatedObject(self, &kThreadSafeKey);
}
}
3. 类型安全封装
// 类型安全的封装
- (void)setTempNumber:(NSNumber *)number {
objc_setAssociatedObject(self,
&kNumberKey,
number,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)tempNumber {
id obj = objc_getAssociatedObject(self, &kNumberKey);
return [obj isKindOfClass:[NSNumber class]] ? obj : nil;
}
4. 自动清理机制
// 基于生命周期的自动清理
__weak typeof(self) weakSelf = self;
[NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
[weakSelf clearAllTempData];
}];
性能考虑
-
关联对象性能:
- 获取/设置速度:O(1) 常量时间
- 内存开销:每个关联对象约 16-32 字节额外开销
- 适合存储中小型数据
-
替代方案比较:
graph LR A[少量临时数据] --> B[关联对象] C[多个属性] --> D[子类化] E[全局状态] --> F[静态字典] G[复杂场景] --> H[代理模式]
常见问题解决
问题:关联对象导致内存泄漏
解决方案:
// 使用弱引用避免循环引用
static char kWeakReferenceKey;
- (void)setWeakReference:(id)object {
objc_setAssociatedObject(self,
&kWeakReferenceKey,
object,
OBJC_ASSOCIATION_ASSIGN); // 注意:ASSIGN有风险
// 更安全的弱引用实现
__weak typeof(object) weakObject = object;
objc_setAssociatedObject(self,
&kWeakReferenceKey,
[^{ return weakObject; } copy],
OBJC_ASSOCIATION_COPY);
}
- (id)weakReference {
id (^block)(void) = objc_getAssociatedObject(self, &kWeakReferenceKey);
return block ? block() : nil;
}
问题:多线程访问冲突
解决方案:
// 使用锁保证线程安全
static char kSafeDataKey;
static NSLock *dataLock;
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataLock = [NSLock new];
});
}
- (void)setSafeData:(id)data {
[dataLock lock];
objc_setAssociatedObject(self,
&kSafeDataKey,
data,
OBJC_ASSOCIATION_RETAIN);
[dataLock unlock];
}
- (id)safeData {
[dataLock lock];
id result = objc_getAssociatedObject(self, &kSafeDataKey);
[dataLock unlock];
return result;
}
总结建议
-
首选关联对象:适合大多数临时数据存储场景
- 使用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
作为默认策略 - 为键值添加前缀避免冲突
- 在
dealloc
中清理关联对象
- 使用
-
复杂场景考虑:
- 需要类型安全 → 使用子类化
- 全局状态管理 → 静态字典+分类
- 复杂数据模型 → 代理模式
-
避免滥用:
- 临时存储应保持短期性
- 定期清理不再使用的数据
- 对于永久存储使用更持久化的方案
-
安全实践:
// 安全的关联对象访问模板 #define DEFINE_ASSOCIATED_OBJECT(type, name, policy) \ - (void)set##name:(type)object { \ objc_setAssociatedObject(self, @selector(name), object, policy); \ } \ - (type)name { \ return objc_getAssociatedObject(self, @selector(name)); \ } // 使用 @interface NSObject (SafeStorage) DEFINE_ASSOCIATED_OBJECT(NSString *, safeString, OBJC_ASSOCIATION_COPY_NONATOMIC) @end
这些方法都经过了生产环境验证,在 iOS 开发中被广泛使用。关联对象是最灵活和常用的解决方案,但在具体实现时需根据场景选择最合适的方案。