NSMutableArray-强制数组仅保留特定的对象类型


68

有没有一种方法可以强制NSMutableArray仅保留一种特定的对象类型?

我有如下的类定义:

@interface Wheel:NSObject  
{    
  int size;  
  float diameter;  
}  
@end  


@interface Car:NSObject  
{  
   NSString *model;  
   NSString *make;  
   NSMutableArray *wheels;  
}  
@end

如何强制阵列仅使用代码保存对象?(绝对不是其他对象)


Answers:


96

2015年更新

这个答案最初是在2011年初写的,然后开始:

我们真正想要的是参数多态性,因此您可以声明NSMutableArray<NSString>:但可惜的是,这种方式不可用。

在2015年,Apple显然通过在Objective-C中引入“轻量级泛型”来改变了这一点,现在您可以声明:

NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];

但是,一切都不尽如人意,请注意“轻量级” ...然后注意,上述声明的初始化部分不包含任何通用符号。尽管Apple引入了参数化集合,并在上述数组中直接添加了非字符串onlyStrings,例如:

[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer 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...

苹果公司实际上实现的是一个提示系统,以协助与S​​wift进行自动互操作,它确实具有类型安全的泛型。但是,在Objective-C方面,尽管编译器提供了一些额外的提示,但系统是“轻量级的”,类型完整性最终仍归程序员所有(这与Objective-C的方式一样)。

那么您应该使用哪个呢?新的轻量级/伪泛型,还是为代码设计自己的模式?确实没有正确的答案,找出在您的方案中有意义的方法并使用它。

例如:如果您打算与Swift互操作,则应使用轻量级泛型!但是,如果集合的类型完整性在您的场景中很重要,那么您可以将轻量级泛型与您在Objective-C端的代码结合起来,从而在Swift方面实现Swift的类型完整性。

2011年答案的其余部分

作为另一个选择,这里是NSMutableArray的快速通用子类,您可以使用单态数组中所需的对象类型来初始化该子类。此选项不会为您提供静态类型检查(与在Obj-C中获得的一样多),您会在插入错误类型时获得运行时异常,就像您获得索引超出范围时的运行时异常等一样。

没有经过彻底测试,并且假定有关覆盖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"];

谢谢,这就是我想要的。
Tuyen Nguyen

+1-基于此代码,我想到了使用块进行测试的想法,是否应将某个对象添加到数组中。这可以是类的成员资格,对象属性的值或虚拟其他任何东西。请看看stackoverflow.com/questions/8190530/...
vikingosegundo

@bendytree-使用宏为每种类型生成一个类以进行静态类型检查是一个有趣的想法。但是,您根本不需要正向调用的东西,只需像上面的示例一样适当地子类化NSMutableArray,它将更加高效。因此,将您的宏与上述内容结合使用,您将获得两者的最佳选择。
CRD

3
@PaulPraet -NSArrayNSMutableArray是一类集群和它们被设计任何亚类必须为元件提供其自己的存储的方式-其可以是一个独立的实例NSArray/NSMutableArray这里使用。请参阅NSArray文档中的“子类化注释” 。
CRD

1
@ BenC.R.Leggiero-答案不是“不正确”,的确,如果您想要单态数组,则答案会在运行时为您提供;新的轻量级泛型使您在编译时更近了,但并不总是如此。是的,语言更改意味着它有点“过时”,因为它没有涵盖自编写以来发生的所有事情,我已经进行了编辑以添加一些细节(在这种情况下这样做有一些优点),但是您经常在真实时间已更改的SO上查找内容。如果提供永久性地进行编辑的答案,那么答案将大大减少!历史可以有价值。:-)
CRD 2015年

30

使用XCode 7泛型现在可以在Objective-C中使用!

因此,您可以声明NSMutableArray为:

NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];

如果您尝试将非Wheel对象放入数组,编译器将向您发出警告。


我认为您已经忘记了*前轮。
NKorotkov

1
我不需要说[[NSMutableArray<Wheel*> alloc] init...]吗?
Ben Leggiero 2015年

1
我们可能都需要参数类型,不幸的是,我们需要阅读Apple的字体;这些是Apple所谓的“轻量级”泛型,实际上仅作为注释提供,以帮助Swift编译器。不幸的是,如果您尝试键入错误的操作,它们并不总是“给您警告”,我更新了答案以提供更多详细信息。
CRD

9

我可能是错的(我是菜鸟),但是我认为,如果您创建一个自定义协议并确保要添加到数组中的对象遵循相同的协议,那么在声明要使用的数组时

NSArray<Protocol Name>

那应该防止添加不遵循所述协议的对象。


3
如果您不知道为什么要回答呢?
Roel

19
这被称为试图帮助其他程序员。
Gravedigga

1
这是错误的,此类型仅允许您分配符合的NSArray子类Protocol Name。因此,它与数组对象本身有关,而与它的内容无关。您应该写信NSArray<id<Protocol Name>>以实现自己的最初目标。
DanSkeel

5

据我所知..在车轮mutableArray中添加任何对象之前,您必须添加一些复选标记。我要添加的对象是“车轮”类。如果是,则添加;否则,则不然。

例:

if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id) 
}

这样的事情。我不记得确切的语法。


2
我同意-不要将nsmutablearray子类化-只需将其隐藏在您的车中,并使仅有的两个方法为addWheel:(Wheel *)aWheel和deleteWheel:。(例如)。
汤姆·安德森

当我说隐藏转轮阵列时,我的意思是将其设置为私有,不将其设置为属性,等等。您可能还需要-(NSArray *)wheels;。调用,此操作将返回[NSArray arrayWithArray:wheels];
汤姆·安德森

3

我希望这会有所帮助(并且可以工作...: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

1
如何初始化轮子?
Jagveer Singh Rajput 2015年

@JagveerSinghRajput,你走了-我为你编辑了答案:)
Aviram Netanel 2015年

2

Xcode 7允许您将数组,字典,甚至您自己的类定义为具有泛型。数组语法如下:

NSArray<NSString*>* array = @[@"hello world"];

1

我不认为有任何方法可以立即使用NSMutableArray。您可能可以通过子类化并覆盖所有构造函数和插入方法来强制执行此操作,但这可能不值得。您希望通过此实现什么?


0

那不可能 NSArray(无论是否可变)将保存任何对象类型。您可以做的就是按照Jim的建议创建自己的自定义子类。另外,如果您想过滤一个数组以删除非所需类型的对象,则可以执行以下操作:

- (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混合的数组划分为两个数组,每个数组仅包含一种对象。


0

如果您没有特定的对象,则可以使用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.
 }
}

0

为了避免将NSMutableArray子类化,我做了以下工作:使用类别。这样,您可以拥有所需的参数和返回类型。请注意命名约定:将要使用的每个方法中的“对象”一词替换为元素类的名称。“ 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

尽管这可以为您提供类型,但实际上并没有将数组的内容限制为您可以直接调用的类型addObject:-这意味着随后的对象wheelAtIndex:可能会铸造轮子以外的其他东西,从而导致轮子混乱。您可以将您的方法与MonomorphicArray其他答案结合起来;只需MonomorphicArray使用带有类型化的getter和setter的类扩展即可。
CRD

0

协议可能是一个好主意:

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

使用:

NSArray<Person>*  personArray;

您测试过代码了吗?为什么NSArray符合Person协议可以进行检查?
johnMa 2014年

仅当您的对象不符合协议时,编译器才会发出警告。
lbsweek 2014年

2
不,不会,实际上Incompatible pointer types initializing 'NSArray<Person> *' with an expression of type 'NSArray *',即使在其中插入Person也会得到。
johnMa 2014年

0

有一个单头文件项目允许这样做: Objective-C-Generics

用法

将ObjectiveCGenerics.h复制到您的项目。定义新类时,请使用GENERICSABLE宏。

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

现在,您可以像在Java,C#等中通常使用的那样,将泛型与数组和集合一起使用。

码: 在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.