94

我可以创建一个所有元素都是 type的NSMutableArray实例吗?SomeClass

4

11 回答 11

150

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

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

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

笔记

需要注意的是,这些只是编译器警告,从技术上讲,您仍然可以将任何对象插入到数组中。有一些脚本可以将所有警告转化为错误,从而阻止构建。

于 2015-06-25T02:29:39.870 回答
53

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

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

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

如果您控制实现您希望调用的方法的实例的源代码,更常见的方法是定义@protocol包含这些方法的 a 并声明相关类在其声明中实现该协议。在这种用法中,a@protocol类似于 Java 接口或 C++ 抽象基类。然后,您可以测试是否符合整个协议,而不是响应每种方法。在前面的示例中,它不会有太大的不同,但是如果您调用多个方法,它可能会简化事情。该示例将是:

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

假设MyProtocol声明myMethod. 第二种方法受到青睐,因为它比第一种更清楚地说明了代码的意图。

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

于 2009-03-16T18:27:18.757 回答
36

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

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

于 2009-03-16T07:00:24.413 回答
11

您可以子类化NSMutableArray以强制执行类型安全。

NSMutableArray是一个类 cluster,所以子类化不是微不足道的。我最终继承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

于 2012-01-08T21:35:57.500 回答
7

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

于 2013-08-08T02:56:02.883 回答
4

这个 Github 项目正是实现了该功能。

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

从他们的例子中:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
于 2013-11-05T11:03:31.367 回答
1

2020,直截了当的回答。碰巧我需要一个类型为NSString.

句法:

Type<ArrayElementType *> *objectName;

例子:

@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
于 2020-09-16T06:44:21.877 回答
0

一种可能的方法是继承 NSArray,但 Apple 建议不要这样做。对类型化 NSArray 的实际需求三思而后行更简单。

于 2009-03-16T09:42:14.260 回答
0

我创建了一个使用 NSArray 对象作为支持 ivar 的 NSArray 子类,以避免 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 方法。

于 2013-03-20T14:37:27.490 回答
0

如果您混合使用 c++ 和 Objective-c(即使用 mm 文件类型),则可以强制使用 pair 或 tuple 键入。例如,在下面的方法中,可以创建一个 std::pair 类型的 C++ 对象,将其转换为 OC wrapper 类型的对象(需要定义的 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];
}
于 2017-12-11T19:22:56.700 回答
0

我的两分钱有点“干净”:

使用类型定义:

typedef NSArray<NSString *> StringArray;

在代码中我们可以这样做:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
于 2018-12-23T11:43:27.113 回答