有没有办法在NSArray,NSMutableArray等上强制输入?

时间:2009-03-16 06:51:26

标签: objective-c generics data-structures collections strong-typing

我可以制作NSMutableArray个实例 all 这些元素的类型为SomeClass吗?

11 个答案:

答案 0 :(得分:138)

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

现在,Objective-C正式支持此功能。从Xcode 7开始,您可以使用以下语法:

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

注意

重要的是要注意这些只是编译器警告,并且您在技术上仍然可以将任何对象插入到数组中。有些脚本可以将所有警告变成错误,从而阻止构建。

答案 1 :(得分:52)

对于人们从强类型语言(如C ++或Java)过渡到更弱或动态类型的语言(如Python,Ruby或Objective-C),这是一个相对常见的问题。在Objective-C中,大多数对象都继承自NSObject(类型id)(其余对象继承自其他根类,如NSProxy,也可以是id类型),任何消息都可以发送到任何对象。当然,将消息发送到它无法识别的实例可能会导致运行时错误(并且还会导致编译器警告具有适当的-W标志)。只要实例响应您发送的消息,您可能不关心它属于哪个类。这通常被称为“鸭子打字”,因为“如果它像鸭子一样嘎嘎叫[即响应选择器],它就是一只鸭子[即它可以处理信息;谁在乎它是什么类]”。

您可以使用-(BOOL)respondsToSelector:(SEL)selector方法测试实例是否在运行时响应选择器。假设您想要在数组中的每个实例上调用一个方法,但不确定所有实例都可以处理该消息(因此您不能只使用NSArray的{​​{3}},这样的事情会工作:

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。第二种方法很受欢迎,因为它比第一种方法更能说明代码的意图。

通常,其中一种方法可以让您免于关心数组中的所有对象是否属于给定类型。如果你仍然关心,标准的动态语言方法是单元测试,单元测试,单元测试。因为此要求中的回归将产生(可能是不可恢复的)运行时(不是编译时)错误,所以您需要具有测试覆盖率来验证行为,以便您不会将崩溃释放到野外。在这种情况下,执行修改数组的操作,然后验证数组中的所有实例是否属于给定的类。通过适当的测试覆盖,您甚至不需要额外的运行时开销来验证实例标识。你确实有很好的单元测试覆盖率,不是吗?

答案 2 :(得分:34)

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

通常,在Objective-C中似乎不需要这样的约束。我不认为我曾经听过有经验的Cocoa程序员对该功能的期望。唯一似乎是来自其他语言的程序员仍在使用这些语言的人。如果只需要数组中给定类的对象,则只在该类中粘贴该类的对象。如果您想测试您的代码是否正常运行,请进行测试。

答案 3 :(得分:11)

您可以继承NSMutableArray以强制执行类型安全。

NSMutableArrayclass cluster,所以子类化并不简单。我最终继承自NSArray并将调用转发到该类中的数组。结果是一个名为ConcreteMutableArray的类,其中 易于子类化。这就是我想出的:

更新:在子类化类集群时签出此blog post from 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

答案 4 :(得分:7)

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

答案 5 :(得分:4)

This Github Project实现了该功能。

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

从他们的例子中可以看出:

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

答案 6 :(得分:0)

可能的方法可能是继承NSArray,但Apple建议不要这样做。想一下类型化NSArray的实际需求,这更简单。

答案 7 :(得分:0)

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

只允许NSString对象,您可以将AddBlock定义为

^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);

这只是一个示例代码,从未在现实世界的应用程序中使用过。要做到这一点,可能需要执行mor NSArray方法。

答案 8 :(得分:0)

如果混合c ++和objective-c(即使用mm文件类型),则可以使用pair或tuple强制键入。例如,在下面的方法中,您可以创建一个类型为std :: pair的C ++对象,将其转换为OC包装器类型的对象(您需要定义的std :: pair包装器),然后将其传递给某些对象其他OC方法,您需要将OC对象转换回C ++对象才能使用它。 OC方法仅接受OC包装类型,从而确保类型安全。您甚至可以使用元组,可变参数模板,类型列表来利用更高级的C ++功能来促进类型安全。

- (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];
}

答案 9 :(得分:0)

我的两分钱可以让他变得“干净”:

使用typedefs:

start = StringVar()
end = StringVar()

Label(root, text="Begin").grid(row=0)
Label(root, text="End").grid(row=1)
e1 = Entry(root, textvariable=start)
e2 = Entry(root, textvariable=end)
e1.grid(row=0, column=1)
e2.grid(row=1, column=1)

b1 = Button(root, text="Test_Button", command=getshift())
b1.grid(row=2, column=1)

在代码中我们可以做到:

typedef NSArray<NSString *> StringArray;

答案 10 :(得分:0)

2020,直接回答。碰巧我需要一个janken类型的可变数组。

语法:

NSString

示例:

Type<ArrayElementType *> *objectName;