前言
在 Objective-C 开发中,我们时常需要使用集合类型来存储多个对象。常见的集合类如 NSArray 和 NSSet 是非常常用的,但它们默认会对元素进行强引用(strong reference)。这意味着只要集合中还保留着对象的引用,该对象就不会被释放。
然而,在某些情况下,我们希望存储对象的同时不干扰其生命周期。这时,NSHashTable 就派上了用场。
下面,我们先来看下什么是 NSHashTable?
什么是 NSHashTable?
NSHashTable 是 Foundation 框架中提供的一个集合类,与 NSSet 类似,但具备更大的灵活性。它不仅可以存储对象,还允许我们定义集合对对象的引用语义,比如下面的支持特性:
- 弱引用
- 可变元素
- 支持非对象类型
那么,什么场景下我们需要用到它呢?
NSHashTable的使用场景
假设你正在实现一个管理监听器的类(比如事件分发器、观察者模式等),你不希望这些监听器因为被存储在集合中而无法释放。NSHashTable 的弱引用特性可以帮助我们:
- 避免循环引用(retain cycle)
- 避免内存泄漏
- 无需手动移除释放的对象
这对于写出健壮、内存安全的代码非常关键。我们可以存储对象但不强引用它们,示例代码如下:
// 声明一个 Person 的类型
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)dealloc {
NSLog(@"Deallocating %@", self.name);
}
@end
// 在控制器中使用 weak HashTable 存储 Person 对象
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) NSHashTable *weakTable;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testP];
}
- (void)testP {
// 创建一个弱引用的 NSHashTable
self.weakTable = [NSHashTable weakObjectsHashTable];
// 创建作用域内的对象
for (int i = 0; i < 3; i++) {
Person *obj = [[Person alloc] init];
obj.name = [NSString stringWithFormat:@"Obj-%d", i];
[self.weakTable addObject:obj];
}
NSLog(@"对象添加后:%@", self.weakTable.allObjects);
}
@end
输出如下:
Deallocating Obj-0
Deallocating Obj-1
Deallocating Obj-2
对象添加后:(
)
通过上面的打印我们可以看到,尽管我们将对象添加到 NSHashTable 中,但由于它是以弱引用的方式存储的,当对象超出作用域并没有其他强引用时,会立即释放。
配置 NSHashTable 的其他方式
使用 NSHashTable 我们还可以通过不同的选项构建出适合需求的集合:
NSHashTable *strongTable = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsStrongMemory capacity:0];
NSHashTable *copyTable = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsCopyIn capacity:0];
但大多数情况下,使用内建方法就足够:
+ (instancetype)weakObjectsHashTable:弱引用,不保留对象。+ (instancetype)hashTableWithOptions::可以自定义引用语义。
实际场景应用:事件监听器
考虑一个经典场景:你有一个“事件中心”类,多个组件注册为监听器。我们希望监听器随时可以释放,而不是被事件中心“卡住”。
@protocol EventListener <NSObject>
- (void)onEventReceived:(NSString *)event;
@end
@interface EventCenter : NSObject
- (void)addListener:(id<EventListener>)listener;
- (void)notifyEvent:(NSString *)event;
@end
@implementation EventCenter {
NSHashTable *_listeners;
}
- (instancetype)init {
self = [super init];
if (self) {
_listeners = [NSHashTable weakObjectsHashTable];
}
return self;
}
- (void)addListener:(id<EventListener>)listener {
[_listeners addObject:listener];
}
- (void)notifyEvent:(NSString *)event {
for (id<EventListener> listener in _listeners) {
[listener onEventReceived:event];
}
}
@end
这样,监听器一旦没有其他强引用,将自动释放,不会引起内存泄漏。
注意事项
NSHashTable只能用于存储对象(不是基本数据类型)。- 使用
weakObjectsHashTable存储的对象若没有强引用,会自动从集合中移除。 - 如果你需要键值对形式的弱引用集合,请使用
NSMapTable。
总结
NSHashTable 它允许我们构建更加灵活、内存友好的集合,尤其在实现弱引用集合时非常实用。使用它可以有效地避免很多典型的内存管理问题,如观察者未移除、循环引用等。