NSMutableArray – 强制数组只保存特定的对象types
有没有办法强制NSMutableArray只保存一个特定的对象types?
我有类的定义如下:
@interface Wheel:NSObject { int size; float diameter; } @end @interface Car:NSObject { NSString *model; NSString *make; NSMutableArray *wheels; } @end
我怎样才能强制wheel数组只能用代码保持Wheel对象? (绝对不是其他的对象)
2015年更新
这个答案是在2011年初写的,开始:
我们真正想要的是参数多态,所以你可以声明,比如
NSMutableArray<NSString>
; 但可惜这是不可用的。
在2015年,苹果显然通过将“轻量级generics”引入到Objective-C中来改变这种状况,现在您可以声明:
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
但是看起来并不完全一样,注意“轻量级”…然后注意上面声明的初始化部分不包含任何通用符号。 虽然苹果引入了参数集合,并直接添加一个非string到上面的数组, onlyStrings
,如:
[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
将如所示的非法警告,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...
苹果所实施的本质上是一个提示系统,以帮助与Swift进行自动交互操作,它具有types安全的generics。 然而,在Objective-C方面,虽然编译器提供了一些额外的提示,但是系统是“轻量级”的,types完整性仍然最终取决于程序员 – 就像Objective-C方式一样。
那么你应该使用哪个? 新的轻量级/伪generics,或为您的代码devise自己的模式? 真的没有正确的答案,找出你的scheme中有什么意义,并使用它。
例如:如果你的目标是与Swift互操作,你应该使用轻量级的generics! 但是,如果集合的types完整性在您的场景中很重要,那么您可以将轻量级generics与您自己的Objective-C代码结合起来,从而强化Swift的types完整性。
2011年答案的剩余部分
作为另一种select,这里是一个NSMutableArray的快速通用子类,您可以在单态数组中使用所需的对象进行初始化。 这个选项不会给你静态的types检查(就像你在Obj-C中得到它一样),你会在插入错误types时得到运行时exception,就像你获得索引超出范围的运行时exception一样。
这是没有彻底的testing,并假设重写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"];
使用XCode 7generics现在可以在Objective-C中使用!
所以你可以声明你的NSMutableArray
为:
NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];
如果您尝试将非Wheel对象放在数组中,编译器会给您一个警告。
我可能是错的(我是noob),但我认为,如果你创build一个自定义协议,并确保你添加到数组的对象遵循相同的协议,那么当你声明你使用的数组
NSArray<Protocol Name>
这应该防止添加不符合上述协议的对象。
根据我知道..在添加任何对象的车轮mutableArray之前,你必须添加一些复选标记。 我添加的对象是类“车轮”。 如果是的话就添加,否则不行。
例:
if([id isClassOf:"Wheel"] == YES) { [array addObject:id) }
像这样的东西。 我不记得确切的语法。
协议也许是一个好主意:
@protocol Person <NSObject> @end @interface Person : NSObject <Person> @end
使用:
NSArray<Person>* personArray;
我希望这将有助于(和工作…: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
Xcode 7允许你定义数组,字典,甚至你自己的类作为generics。 数组语法如下所示:
NSArray<NSString*>* array = @[@"hello world"];
我不相信有什么办法用NSMutableArray
开箱即可。 你也许可以通过inheritance和覆盖所有的构造函数和插入方法来强制执行,但这可能不值得。 你希望用这个做什么?
这是不可能的; 一个NSArray(无论是否可变)将保存任何对象types。 你可以做的是按照Jim的build议创build你自己的自定义子类。 另外,如果你想过滤一个数组来移除那些不是你想要的types的对象,那么你可以这样做:
- (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混合的数组分成两个数组,每个数组只包含一种对象。
如果你没有特定的对象,你可以使用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. } }
这里有一些我已经做了避免NSMutableArray的子类:使用一个类别。 这样你可以有你想要的参数和返回types。 请注意命名约定:将要使用的每个方法中的单词“对象”replace为元素类的名称。 “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
有一个头文件项目,它允许这个: Objective-C-generics
用法 :
将ObjectiveCGenerics.h复制到您的项目中。 当定义一个新类时使用GENERICSABLEmacros。
#import "ObjectiveCGenerics.h" GENERICSABLE(MyClass) @interface MyClass : NSObject<MyClass> @property (nonatomic, strong) NSString* name; @end
现在,您可以像使用Java,C#等一样使用generics和数组。
码: