【问题标题】:NSMutableArray - force the array to hold specific object type onlyNSMutableArray - 强制数组只保存特定的对象类型
【发布时间】:2011-07-09 00:29:42
【问题描述】:

有没有办法强制 NSMutableArray 只保存一种特定的对象类型?

我的类定义如下:

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


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

如何强制 wheels 数组仅使用代码来保存 Wheel 对象? (绝对不是其他对象)

【问题讨论】:

标签: objective-c cocoa nsmutablearray


【解决方案1】:

我不相信开箱即用的NSMutableArray 可以做到这一点。您可能可以通过继承和覆盖所有构造函数和插入方法来强制执行此操作,但这可能不值得。你希望通过这个实现什么目标?

【讨论】:

    【解决方案2】:

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

    例子:

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

    类似的东西。我不记得确切的语法。

    【讨论】:

    • 我同意 - 不要继承 nsmutablearray - 只需将其隐藏在您的汽车中,并使用仅有的两个方法 addWheel:(Wheel*)aWheel 和 deleteWheel:。 (例如)。
    • 当我说隐藏轮子数组时,我的意思是将其设为私有,不要将其设为属性等。您可能还需要一个 -(NSArray*)wheels;调用,它会返回 [NSArray arrayWithArray:wheels];
    【解决方案3】:

    这是不可能的; 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 混合的数组分成两个数组,每个数组只包含一种对象。

    【讨论】:

      【解决方案4】:

      2015 年更新

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

      我们真正想要的是参数多态性,所以你可以声明,比如说,NSMutableArray&lt;NSString&gt;;但遗憾的是不可用。

      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...
      

      Apple 实现的本质上是一个提示系统,以帮助与 Swift 进行自动互操作,它确实具有类型安全泛型的味道。然而,在 Objective-C 方面,虽然编译器提供了一些额外的提示,但系统是“轻量级的”,类型完整性最终仍然取决于程序员——就像 Objective-C 的方式一样。

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

      例如:如果您的目标是与 Swift 互操作,您应该使用轻量级泛型!但是,如果集合的类型完整性在您的场景中很重要,那么您可以在 Objective-C 端将轻量级泛型与您自己的代码结合起来,从而强制实现 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"];
      

      【讨论】:

      • 谢谢,这就是我要找的。​​span>
      • +1 — 基于这段代码,我想到了使用块来测试是否应该将某个对象添加到数组中。这可以是类的成员资格、对象属性的值或虚拟的其他任何东西。请看:stackoverflow.com/questions/8190530/…
      • @bendytree - 使用宏为每种类型生成一个类以进行静态类型检查是一个有趣的想法。但是,您根本不需要前向调用的东西,只需像上面的示例中那样正确地继承 NSMutableArray ,它会更有效率。因此,将您的宏与上述内容结合起来,您将获得两者的最佳效果。
      • @PaulPraet - NSArrayNSMutableArray 是一个 类集群 并且它们的设计方式任何子类都必须为元素提供自己的存储 - 这可以是此处使用的 NSArray/NSMutableArray 的单独实例。请参阅NSArray 文档中的“子类化注释”。
      • @BenC.R.Leggiero - 答案不是“不正确”,事实上,如果你想要一个单态数组,答案在运行时会给你;虽然新的轻量级泛型在编译时让您更接近,但并非一直如此。是的,语言更改意味着它有点“过时”,因为它没有涵盖自编写以来发生的事情,我已经编辑以添加一些细节(因为在这种情况下这样做有一些优点)但你会经常在 SO 上查找其真实性时间已更改的内容。如果提供答案会导致永久编辑,那么答案会少得多!历史上可以有价值。 :-)
      【解决方案5】:

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

      NSArray<Protocol Name>
      

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

      【讨论】:

      • 不知道为什么要回答这个问题?
      • 它被称为试图帮助其他程序员。
      • 这是错误的,这种类型只允许你分配符合Protocol Name的NSArray子类。所以它与数组对象本身有关,而不是它的内容。你应该写NSArray&lt;id&lt;Protocol Name&gt;&gt;来实现你的最初目标。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        为了避免继承 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 结合起来;只需使用具有类型化 getter 和 setter 的类扩展 MonomorphicArray
        【解决方案8】:

        协议可能是个好主意:

        @protocol Person <NSObject>
        @end
        
        @interface Person : NSObject <Person>
        @end
        

        使用:

        NSArray<Person>*  personArray;
        

        【讨论】:

        • 你测试你的代码了吗??为什么 NSArray 符合 Person 协议可以做检查?
        • 只有当你的对象不符合协议时,编译器才会给出警告。
        • 不,不会,事实上你会得到Incompatible pointer types initializing 'NSArray&lt;Person&gt; *' with an expression of type 'NSArray *',即使你在其中插入Person。
        【解决方案9】:

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

        【讨论】:

        • 如何初始化轮子?
        • @JagveerSinghRajput 给你 - 我为你编辑了我的答案:)
        【解决方案10】:

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

        用法

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

        #import "ObjectiveCGenerics.h"
        
        GENERICSABLE(MyClass)
        
        @interface MyClass : NSObject<MyClass>
        
        @property (nonatomic, strong) NSString* name;
        
        @end
        

        现在您可以像在 Java、C# 等中一样使用泛型和数组和集合。

        代码:

        【讨论】:

          【解决方案11】:

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

          所以您可以将您的NSMutableArray 声明为:

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

          如果您尝试将非 Wheel 对象放入数组中,编译器会给您一个警告。

          【讨论】:

          • 我想你在轮子之前忘记了*
          • 我不需要说[[NSMutableArray&lt;Wheel*&gt; alloc] init...]吗?
          • 我们可能都想要参数类型,不幸的是我们需要阅读Apple的小字体;这些是 Apple 所称的“轻量级”泛型,本质上仅作为 cmets 存在以协助 Swift 编译器。不幸的是,如果您尝试输入错误的操作,他们并不总是“给您警告”,我已经更新了我的答案以提供更多详细信息。
          【解决方案12】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-09-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-01-02
            • 2012-03-07
            • 2013-12-05
            相关资源
            最近更新 更多