有没有办法强制在NSArray,NSMutableArray等等上打字?

我可以做一个NSMutableArray的所有元素是SomeClasstypes?

您可以使用-addSomeClass:方法创build一个类别,以允许进行编译时静态types检查(因此编译器可以让您知道,如果您尝试通过该方法添加一个对象,它知道这是一个不同的类),但是没有真正的方法强制一个数组只包含给定类的对象。

一般来说,在Objective-C中似乎不需要这样的约束。 我不认为我曾经听过有经验的Cocoa程序员希望这个function。 只有那些似乎是其他语言的程序员仍然用这些语言思考的人。 如果你只想要一个给定的类的对象在数组中,只有那个类的对象在那里。 如果您想testing您的代码是否正常运行,请对其进行testing。

没有人把它放在这里,所以我会做!

Objective-C现在已经正式支持这个了。 从Xcode 7开始,您可以使用以下语法:

 NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]]; 

注意

需要注意的是,这些只是编译器警告,技术上仍然可以将任何对象插入到数组中。 有可用的脚本,将所有的警告转化为错误,这将防止build设。

对于从强types语言(如C ++或Java)转换为更弱或者dynamictypes的语言,如Python,Ruby或Objective-C,这是一个相对常见的问题。 在Objective-C中,大多数对象inheritance自NSObject (typesid )(其余的inheritance自NSProxy等其他根类,也可以是typesid ),任何消息都可以发送到任何对象。 当然,将消息发送给不能识别的实例可能会导致运行时错误(也会导致带有相应-W标志的编译器警告 )。 只要一个实例响应您发送的消息,您可能不在乎它属于哪个类。 这通常被称为“鸭子打字”,因为“如果它像鸭子一样呱呱叫(即响应select器),它就是一只鸭子(即它可以处理信息;谁在乎它是什么类别)”。

您可以使用-(BOOL)respondsToSelector:(SEL)selector方法testing实例是否在运行时-(BOOL)respondsToSelector:(SEL)selector 。 假设你想调用一个数组中的每个实例的方法,但不确定所有的实例都可以处理这个消息(所以你不能只使用NSArray-[NSArray makeObjectsPerformSelector:] ,这样的事情会工作:

 for(id o in myArray) { if([o respondsToSelector:@selector(myMethod)]) { [o myMethod]; } } 

如果您控制实现您希望调用的方法的实例的源代码,则更常见的方法是定义包含这些方法的@protocol ,并声明所讨论的类在声明中实现该协议。 在这种用法中, @protocol类似于Java接口或C ++抽象基类。 然后,您可以testing是否符合整个协议,而不是响应每种方法。 在前面的例子中,它没有什么区别,但是如果你调用多个方法,可能会简化一些东西。 这个例子将是:

 for(id o in myArray) { if([o conformsToProtocol:@protocol(MyProtocol)]) { [o myMethod]; } } 

假设MyProtocol声明myMethod 。 这个第二种方法是有利的,因为它比第一个更清楚地说明了代码的意图。

通常,这些方法之一将您从关心数组中的所有对象是否为给定types中解放出来。 如果你仍然在意,标准的dynamic语言方法是unit testing,unit testing,unit testing。 因为这个需求的回归会产生(很可能是不可恢复的)运行时(不是编译时间)的错误,所以你需要有testing覆盖来validation这个行为,这样你才不会把崩溃放到野外。 在这种情况下,执行修改数组的操作,然后validation数组中的所有实例属于给定的类。 有了适当的testing覆盖率,您甚至不需要增加validation实例身份的运行时间开销。 你有很好的unit testing覆盖率,不是吗?

你可以NSMutableArray来强制types安全。

NSMutableArray是一个类集群 ,所以子类化不是微不足道的。 我最终从NSArrayinheritance,并将调用转发到该类中的数组。 结果是一个名为ConcreteMutableArray的类,它容易inheritance。 以下是我想到的:

  • CustomArray.h
  • CustomArray.m

更新: 从Mike Ash的子类化集群中检出这篇博文 。

将这些文件包含在您的项目中,然后使用macros生成您希望的任何types:

MyArrayTypes.h

 CUSTOM_ARRAY_INTERFACE(NSString) CUSTOM_ARRAY_INTERFACE(User) 

MyArrayTypes.m

 CUSTOM_ARRAY_IMPLEMENTATION(NSString) CUSTOM_ARRAY_IMPLEMENTATION(User) 

用法:

 NSStringArray* strings = [NSStringArray array]; [strings add:@"Hello"]; NSString* str = [strings get:0]; [strings add:[User new]]; //compiler error User* user = [strings get:0]; //compiler error 

其他想法

  • 它从NSArrayinheritance来支持序列化/反序列化
  • 根据你的口味,你可能想要覆盖/隐藏一般的方法,如

    - (void) addObject:(id)anObject

看看https://github.com/tomersh/Objective-C-Generics,Objective-C的一个编译时(预处理器实现的)generics实现。; 这个博客文章有一个很好的概述。 基本上你会得到编译时检查(警告或错误),但没有运行时惩罚generics。

这个Github项目实现了这个function。

然后可以使用<>括号,就像在C#中一样。

从他们的例子:

 NSArray<MyClass>* classArray = [NSArray array]; NSString *name = [classArray lastObject].name; // No cast needed 

一种可能的方式可能是inheritanceNSArray,但苹果build议不要这样做。 对于一个types化的NSArray的实际需求,思考一下就简单多了。

我创build了一个使用NSArray对象作为后援的NSArray子类,以避免NSArray的类集群性质的问题。 它需要块来接受或拒绝添加一个对象。

只允许NSString对象,你可以定义一个AddBlock

 ^BOOL(id element) { return [element isKindOfClass:[NSString class]]; } 

您可以定义一个FailBlock来决定如何处理,如果某个元素testing失败 – 正常过滤失败,将其添加到另一个数组,或者 – 这是默认值 – 引发exception。

VSBlockTestedObjectArray.h

 #import <Foundation/Foundation.h> typedef BOOL(^AddBlock)(id element); typedef void(^FailBlock)(id element); @interface VSBlockTestedObjectArray : NSMutableArray @property (nonatomic, copy, readonly) AddBlock testBlock; @property (nonatomic, copy, readonly) FailBlock failBlock; -(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity; -(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock; -(id)initWithTestBlock:(AddBlock)testBlock; @end 

VSBlockTestedObjectArray.m

 #import "VSBlockTestedObjectArray.h" @interface VSBlockTestedObjectArray () @property (nonatomic, retain) NSMutableArray *realArray; -(void)errorWhileInitializing:(SEL)selector; @end @implementation VSBlockTestedObjectArray @synthesize testBlock = _testBlock; @synthesize failBlock = _failBlock; @synthesize realArray = _realArray; -(id)initWithCapacity:(NSUInteger)capacity { if (self = [super init]) { _realArray = [[NSMutableArray alloc] initWithCapacity:capacity]; } return self; } -(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity { self = [self initWithCapacity:capacity]; if (self) { _testBlock = [testBlock copy]; _failBlock = [failBlock copy]; } return self; } -(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock { return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0]; } -(id)initWithTestBlock:(AddBlock)testBlock { return [self initWithTestBlock:testBlock FailBlock:^(id element) { [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element]; } Capacity:0]; } - (void)dealloc { [_failBlock release]; [_testBlock release]; self.realArray = nil; [super dealloc]; } - (void) insertObject:(id)anObject atIndex:(NSUInteger)index { if(self.testBlock(anObject)) [self.realArray insertObject:anObject atIndex:index]; else self.failBlock(anObject); } - (void) removeObjectAtIndex:(NSUInteger)index { [self.realArray removeObjectAtIndex:index]; } -(NSUInteger)count { return [self.realArray count]; } - (id) objectAtIndex:(NSUInteger)index { return [self.realArray objectAtIndex:index]; } -(void)errorWhileInitializing:(SEL)selector { [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)]; } - (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;} - (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;} - (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;} - (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;} - (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;} - (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;} @end 

像这样使用它:

 VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) { return [element isKindOfClass:[NSString class]]; } FailBlock:^(id element) { NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element); }]; VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) { return [element isKindOfClass:[NSNumber class]]; } FailBlock:^(id element) { NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element); }]; [stringArray addObject:@"test"]; [stringArray addObject:@"test1"]; [stringArray addObject:[NSNumber numberWithInt:9]]; [stringArray addObject:@"test2"]; [stringArray addObject:@"test3"]; [numberArray addObject:@"test"]; [numberArray addObject:@"test1"]; [numberArray addObject:[NSNumber numberWithInt:9]]; [numberArray addObject:@"test2"]; [numberArray addObject:@"test3"]; NSLog(@"%@", stringArray); NSLog(@"%@", numberArray); 

这只是一个示例代码,并没有在现实世界的应用程序中使用。 这样做可能需要实施mor NSArray方法。