我可以创建一个NSMutableArray
实例,其中所有元素都是SomeClass
类型的吗?
我可以创建一个NSMutableArray
实例,其中所有元素都是SomeClass
类型的吗?
还没有人将这个放在这里,所以我来发布!
现在在Objective-C中官方支持此功能。从Xcode 7开始,您可以使用以下语法:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
注意
需要注意的是,这些仅仅是编译器警告,你仍然可以在数组中插入任何对象。有一些脚本可以将所有警告变成错误,从而防止构建。
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
看起来有点笨重,但很实用! - Logan__kindof
可能允许子类?我注意到在iOS 9.0 UINavigationController.h:64中:@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;
- tboyce12NSObject
(类型为id
)(其余继承自其他根类,例如NSProxy
,也可以是类型id
),并且可以向任何对象发送任何消息。当然,向一个实例发送它不识别的消息可能会导致运行时错误(并且还会在适当的-W标志下导致编译器警告)。只要一个实例响应您发送的消息,您可能不关心它属于什么类。这通常被称为“鸭子类型”,因为“如果它像鸭子一样叫[即响应一个选择器],它就是一只鸭子[即它可以处理该消息;谁关心它属于什么类]”。-(BOOL)respondsToSelector:(SEL)selector
方法在运行时测试一个实例是否响应一个选择器。假设您想调用数组中每个实例的一个方法,但不确定所有实例是否都能处理该消息(因此不能只使用NSArray
的-[NSArray makeObjectsPerformSelector:]
方法),则可以使用以下代码:for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
如果您控制实现所需调用方法的实例的源代码,则更常见的方法是定义一个包含这些方法的协议@protocol
,并在声明中指定相关类实现该协议。在这种情况下,@protocol
类似于Java接口或C++抽象基类。然后,您可以测试是否符合整个协议而不是每个方法的响应。在前面的示例中,这不会有太大的区别,但如果要调用多个方法,则可能会简化操作。示例如下:for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
假设MyProtocol
声明了myMethod
。更倾向于第二种方法,因为它比第一种方法更清晰地阐明了代码的意图。
通常情况下,其中一种方法可以使你不必关心数组中的所有对象是否都属于给定类型。如果您仍然关心,则标准动态语言方法是进行单元测试,单元测试和再单元测试。因为在此要求中的回归将产生(很可能无法恢复的)运行时(而非编译时)错误,所以需要具备测试覆盖率以验证行为,以便您不会发布一个会崩溃的版本。在这种情况下,执行修改数组的操作,然后验证数组中的所有实例都属于给定类。有了适当的测试覆盖率,您甚至无需额外的运行时开销来验证实例标识。您确保已经有良好的单元测试覆盖率了,对吧?
你可以使用一个 -addSomeClass:
方法创建一个类别,以允许编译时静态类型检查(这样编译器就可以让你知道如果通过该方法添加已知是不同类的对象),但是没有真正的方法来强制要求数组只包含给定类的对象。
总的来说,在 Objective-C 中似乎并不需要这样的限制。我认为我从来没有听过有经验的 Cocoa 程序员希望拥有这个功能。唯一似乎需要的人是来自其他语言的程序员,他们仍然在使用这些语言进行思考。如果你只想在数组中使用给定类的对象,只需将该类的对象放入其中。如果你想测试你的代码是否正常运行,请对其进行测试。
您可以通过继承NSMutableArray
来强制实现类型安全。
NSMutableArray
是一个类簇,因此子类化并不简单。我最终从NSArray
继承,并将调用转发到该类内部的一个数组。结果产生了一个名为ConcreteMutableArray
的类,它非常容易进行子类化。这是我的解决方案:
更新:查看Mike Ash的博客文章,了解有关子类化类簇的更多信息。
将这些文件包含在您的项目中,然后使用宏生成任何所需的类型:
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
其他想法
NSArray
以支持序列化/反序列化根据您的口味,您可能希望覆盖/隐藏通用方法,例如
- (void)addObject:(id)anObject
这个Github项目实现了这个功能。
你可以使用<>
括号,就像在C#中一样。
以下是他们的示例:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
2020年,简单明了的回答。恰好我需要一个类型为NSString
的可变数组。
语法:
Type<ArrayElementType *> *objectName;
例子:
@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);
ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
[self performSelector:@selector(selectRow:) withObject:oCTableRow];
}
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
FailBlock
来决定如果元素未通过测试应该执行什么操作 - 优雅地过滤掉它,将其添加到另一个数组中,或者 - 这是默认设置 - 抛出异常。
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);