Objective-C 的 NSHashTable 是什么

230 阅读3分钟

前言

在 Objective-C 开发中,我们时常需要使用集合类型来存储多个对象。常见的集合类如 NSArrayNSSet 是非常常用的,但它们默认会对元素进行强引用(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 它允许我们构建更加灵活、内存友好的集合,尤其在实现弱引用集合时非常实用。使用它可以有效地避免很多典型的内存管理问题,如观察者未移除、循环引用等。