在 OC 类中临时保存值的解决方案

在 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];
}];

性能考虑

  1. 关联对象性能

    • 获取/设置速度:O(1) 常量时间
    • 内存开销:每个关联对象约 16-32 字节额外开销
    • 适合存储中小型数据
  2. 替代方案比较

    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;
}

总结建议

  1. 首选关联对象:适合大多数临时数据存储场景

    • 使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC 作为默认策略
    • 为键值添加前缀避免冲突
    • dealloc 中清理关联对象
  2. 复杂场景考虑

    • 需要类型安全 → 使用子类化
    • 全局状态管理 → 静态字典+分类
    • 复杂数据模型 → 代理模式
  3. 避免滥用

    • 临时存储应保持短期性
    • 定期清理不再使用的数据
    • 对于永久存储使用更持久化的方案
  4. 安全实践

    // 安全的关联对象访问模板
    #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 开发中被广泛使用。关联对象是最灵活和常用的解决方案,但在具体实现时需根据场景选择最合适的方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容