2015 年更新
这个答案最初是在 2011 年初写的,开始于:
我们真正想要的是参数多态性,所以你可以声明,比如说,NSMutableArray<NSString>;但遗憾的是不可用。
2015 年,Apple 显然改变了这一点,在 Objective-C 中引入了“轻量级泛型”,现在您可以声明:
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
但一切并不像看起来那样,注意“轻量级”......然后注意上述声明的初始化部分不包含任何通用符号。虽然 Apple 已经引入了参数集合,并在上述数组中添加了一个非字符串直接,onlyStrings,例如:
[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
将显示非法警告,类型安全性几乎是肤浅的。考虑方法:
- (void) push:(id)obj onto:(NSMutableArray *)array
{
[array addObject:obj];
}
以及同一类的另一个方法中的代码片段:
NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:@"asda" onto:oops]; // add a string, fine
[self push:@42 onto:oops]; // add a number, no warnings...
Apple 实现的本质上是一个提示系统,以帮助与 Swift 进行自动互操作,它确实具有类型安全泛型的味道。然而,在 Objective-C 方面,虽然编译器提供了一些额外的提示,但系统是“轻量级的”,类型完整性最终仍然取决于程序员——就像 Objective-C 的方式一样。
那么你应该使用哪个?新的轻量级/伪泛型,还是为您的代码设计自己的模式?真的没有正确的答案,找出在你的场景中什么是有意义的并使用它。
例如:如果您的目标是与 Swift 互操作,您应该使用轻量级泛型!但是,如果集合的类型完整性在您的场景中很重要,那么您可以在 Objective-C 端将轻量级泛型与您自己的代码结合起来,从而强制实现 Swift 将在其端的类型完整性。
2011 年答案的剩余部分
这里的另一个选项是 NSMutableArray 的快速通用子类,您可以在单态数组中使用您想要的对象类型来初始化它。此选项不会为您提供静态类型检查(与您在 Obj-C 中获得的一样多),您会在插入错误类型时获得运行时异常,就像您因索引越界等而获得运行时异常一样。
这没有经过彻底测试,并假定有关覆盖 NSMutableArray 的文档是正确的...
@interface MonomorphicArray : NSMutableArray
{
Class elementClass;
NSMutableArray *realArray;
}
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;
@end
以及实现:
@implementation MonomorphicArray
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
elementClass = element;
realArray = [NSMutableArray arrayWithCapacity:numItems];
return self;
}
- (id) initWithClass:(Class)element
{
elementClass = element;
realArray = [NSMutableArray new];
return self;
}
// override primitive NSMutableArray methods and enforce monomorphism
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
{
[realArray insertObject:anObject atIndex:index];
}
else
{
NSException* myException = [NSException
exceptionWithName:@"InvalidAddObject"
reason:@"Added object has wrong type"
userInfo:nil];
@throw myException;
}
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[realArray removeObjectAtIndex:index];
}
// override primitive NSArray methods
- (NSUInteger) count
{
return [realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [realArray objectAtIndex:index];
}
// block all the other init's (some could be supported)
static id NotSupported()
{
NSException* myException = [NSException
exceptionWithName:@"InvalidInitializer"
reason:@"Only initWithClass: and initWithClass:andCapacity: supported"
userInfo:nil];
@throw myException;
}
- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }
@end
用作:
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];
[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];