有没有办法强制NSMutableArray只保存一个特定的对象类型?
我的课程定义如下:
@interface Wheel:NSObject
{
int size;
float diameter;
}
@end
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray *wheels;
}
@end
如何强制 wheel 数组仅使用代码保存 Wheel 对象? (绝对不是其他对象)
答案 0 :(得分:92)
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的自动交互,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"];
答案 1 :(得分:28)
现在可以在Objective-C中使用XCode 7泛型!
因此,您可以将NSMutableArray
声明为:
NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];
如果您尝试将非Wheel对象放入数组中,编译器会给出警告。
答案 2 :(得分:9)
我可能是错的(我是菜鸟),但我认为,如果你创建一个自定义协议并确保你添加到数组的对象遵循相同的协议,那么当你声明数组时,你使用< / p>
NSArray<Protocol Name>
这应该防止添加不遵循所述协议的对象。
答案 3 :(得分:5)
据我所知..在你将任何对象添加到轮子mutableArray之前,你必须添加一些复选标记。我添加的对象是类“轮”。如果它然后添加,否则不是。
示例:
if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id)
}
像这样的东西。我不记得确切的语法。
答案 4 :(得分:3)
我希望这会有所帮助(和工作......:P)
Wheel.h 档案:
@protocol Wheel
@end
@interface Wheel : NSObject
@property ...
@end
Car.h 档案:
#import "Wheel.h"
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray<Wheel, Optional> *wheels;
}
@end
Car.m 档案:
#import "Car.h"
@implementation Car
-(id)init{
if (self=[super init]){
self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
}
return self;
}
@end
答案 5 :(得分:2)
Xcode 7允许您将Arrays,Dictionaries甚至您自己的Classes定义为具有泛型。数组语法如下:
NSArray<NSString*>* array = @[@"hello world"];
答案 6 :(得分:1)
我不相信有任何方法可以使用NSMutableArray
开箱即用。您可以通过子类化和重写所有构造函数和插入方法来强制执行此操作,但它可能不值得。你有什么希望用这个来实现的?
答案 7 :(得分:0)
那是不可能的; NSArray(无论是否可变)将保存任何对象类型。您可以做的是创建自己的自定义子类,如Jim所建议的那样。或者,如果您想过滤一个数组以删除不属于您想要的类型的对象,那么您可以这样做:
- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
int c = 0;
while(c < [array length])
{
NSObject *object = [array objectAtIndex:c];
if([object isKindOfClass:type])
c++;
else
[array removeObjectAtIndex:c];
}
}
...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];
或者根据isKindOfClass的结果做出其他判断:将包含Cars和Wheels混合的数组划分为两个数组,每个数组只包含一种对象。
答案 8 :(得分:0)
如果您没有特定对象,则可以使用nsexception。
for (int i = 0; i<items.count;i++) {
if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
{
// do something..!
}else{
[NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items];
// do whatever to handle or raise your exception.
}
}
答案 9 :(得分:0)
这是我为避免继承NSMutableArray而做的事情:使用类别。这样您就可以获得所需的参数和返回类型。请注意命名约定:将要使用的每个方法中的单词“object”替换为元素类的名称。 “objectAtIndex”变为“wheelAtIndex”等等。这种方式没有名称冲突。非常整洁。
typedef NSMutableArray WheelList;
@interface NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
@end
@implementation NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index
{
return (wheel *) [self objectAtIndex: index];
}
- (void) addWheel: (wheel *) w
{
[self addObject: w];
}
@end
@interface Car : NSObject
@property WheelList *wheels;
@end;
@implementation Car
@synthesize wheels;
- (id) init
{
if (self = [super init]) {
wheels = [[WheelList alloc] initWithCapacity: 4];
}
return self;
}
@end
答案 10 :(得分:0)
协议可能是一个好主意:
@protocol Person <NSObject>
@end
@interface Person : NSObject <Person>
@end
使用:
NSArray<Person>* personArray;
答案 11 :(得分:0)
有一个头文件项目允许这样: 的 Objective-C-Generics 强>
<强>用法强>:
将ObjectiveCGenerics.h复制到您的项目中。 定义新类时,请使用GENERICSABLE宏。
#import "ObjectiveCGenerics.h"
GENERICSABLE(MyClass)
@interface MyClass : NSObject<MyClass>
@property (nonatomic, strong) NSString* name;
@end
现在,您可以像使用Java,C#等一样使用泛型和数组。
<强>代码:强>