【问题标题】:Should I subclass the NSMutableArray class我应该继承 NSMutableArray 类吗
【发布时间】:2011-04-21 05:29:25
【问题描述】:

我有一个想要添加自定义方法的 NSMutableArray 对象。我尝试对 NSMutableArray 进行子类化,但在尝试使用 count 方法获取对象数量时出现错误消息“仅为抽象类定义的方法”。为什么不继承count方法?

我在其他地方读到,如果我想使用它们,我必须将一些 NSMutableArray 方法导入到我的自定义类中。我只想向 NSMutableArray 类添加一个自定义方法。那么我应该继承 NSMutableArray,还是应该做其他事情?

【问题讨论】:

    标签: objective-c cocoa-touch methods nsmutablearray subclass


    【解决方案1】:

    NSMutableArray 不是一个具体的类,它只是一个类簇的抽象超类。 NSMutableArray 的文档确实包含有关如何子类化的信息,但也强烈建议您不要这样做!仅当您对实际存储有特殊需求时才进行子类化。

    类集群意味着实际的类将在运行时被选择。创建为空的数组可能与使用 1000 个项目创建的数组使用的类不同。运行时可以明智地选择要使用的实现。在实践中,NSMutableArray 将是一个桥接的CFArray。您无需担心,但如果您在调试器中检查数组的类型,您可能会看到它,您永远不会看到 NSArray,但经常看到 NSCFArray

    如前所述,子类化与扩展类不同。 Objective-C 有 categories 的概念。类别类似于其他编程语言所称的 mix-ins。

    例如,如果您希望 NSMutableArray 上的便捷方法对属性上的所有成员进行排序,则在 .h 文件中定义类别接口,如下所示:

    @interface NSMutableArray (CWFirstnameSort)
    -(void)sortObjectsByProperty:(NSString*)propertyName;
    @end
    

    而实现是:

    @implementation NSMutableArray (CWFirstnameSort)
    -(void)sortObjectsByProperty:(NSString*)propertyName;
    {
        NSSortDescriptor* sortDesc = [NSSortDescriptor sortDescriptorWithKey:propertName ascending:YES];
        [self sortUsingDescriptors:[NSArray arrayWithObject:sortDesc]];
    }
    @end
    

    然后简单地使用它:

    [people sortObjectsByProperty:@"firstName"];
    

    【讨论】:

      【解决方案2】:

      如果您只是添加自定义方法,请使用NSMutableArray 上的类别。它是一个类集群,因此实现由未记录的子类提供。您需要提供一些方法来生成您自己的子类。但是,如果您只是添加一个类别,那么您的自定义方法将适用于您应用中的 all NSMutableArrays

      为了比较,这是我不久前写的 implementing a custom NSMutableArray subclass 的一个例子。

      【讨论】:

      • 我想评论一下,实现自定义类型安全的 NSMutableArray 子类是浪费时间。花时间编写这个自定义子类会更好地用于编写一些单元测试来验证您的实际应用程序逻辑是否合理,而不是添加更多混乱。您的额外代码只是需要维护更多代码,只会为错误和安全漏洞添加更多攻击向量,而不会为最终用户带来任何实际好处。
      • @PeyloW:一个很好的理论立场,但最终存在类型安全可能有用的现实世界代码。正如我写的帖子中所述,您将任何故障提前到注入的位置,而不是稍后任意时间。通过消除恶意对象替换的可能性,这减少了攻击面。
      • @PeyloW:还有实际代码需要与 CoreFoundation 交互。这关心您的可变数组实现是否实际上是 NSMutableArray 子类或其他东西;此时你的实现最好继承 NSMutableArray。
      【解决方案3】:

      Objective-C 有一种将方法添加到现有类的机制,称为Categories。这样您就不必创建自己的子类。

      【讨论】:

        【解决方案4】:

        这是一篇旧帖子,但我想补充一下我的经验。 @PayloW 的回答很好,我认为完美地回答了你的问题,但是,没有人真正反过来回答你的问题,所以我会在这里做。

        您应该继承 NSMutableArray(或 NSArray)吗?取决于您想要实现的目标。如果您只想添加一种方法来扩展数组的 BASIC 功能,例如排序,那么@PayloW 的答案Categories 就是这样。但是,如果您想创建一个行为类似于数组的自定义类,那么可以,子类化 NSMutableArray 非常容易。但是因为它是一个类集群,所以它并不像您期望的那样完全子类化。通常在子类化中,超类中可用的方法对您的子类可用,或者您可以覆盖它们。对于类集群,您必须改为包含您将要使用的 Super 方法,并提供超类的 _backend 实例来包装这些方法。

        以下是您如何将NSMutableArray(或任何类集群)子类化的示例:

        界面:

        @interface MyCustomArrayClass : NSMutableArray {
        
            // Backend instance your class will be using
            NSMutableArray *_backendArray;
        }
        
        // *** YOUR CUSTOM METHODS HERE (no need to put the Super's methods here) ***
        
        -(bool)isEmpty;
        -(id)nameAtIndex:(int)index;
        -(int)rowAtIndex:(int)index;
        -(int)columnAtIndex:(int)index;
        
        @end
        

        实施:

        @implementation MyCustomArrayClass
        
        -(instancetype)init {
        
            if (self = [super init]) {            
                _backendArray = [@[] mutableCopy];
            }
        
            return self;
        }
        
        // *** Super's Required Methods (because you're going to use them) ***
        
        -(void)addObject:(id)anObject {
            [_backendArray addObject:anObject];
        }
        
        -(void)insertObject:(id)anObject atIndex:(NSUInteger)index {
            [_backendArray insertObject:anObject atIndex:index];
        }
        
        -(void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
            [_backendArray replaceObjectAtIndex:index withObject:anObject];
        }
        
        -(id)objectAtIndex:(NSUInteger)index {
            return [_backendArray objectAtIndex:index];
        }
        
        -(NSUInteger)count {
            return _backendArray.count;
        }
        
        -(void)removeObject:(id)anObject {
            [_backendArray removeObject:anObject];
        }
        
        -(void)removeLastObject {
            [_backendArray removeLastObject];
        }
        
        -(void)removeAllObjects {
            [_backendArray removeAllObjects];
        }
        
        -(void)removeObjectAtIndex:(NSUInteger)index {
            [_backendArray removeObjectAtIndex:index];
        }
        
        // *** YOUR CUSTOM METHODS ***
        
        -(bool)isEmpty {
            return _backendArray.count == 0;
        }
        
        -(id)nameAtIndex:(int)index {
            return ((MyObject *)_backendArray[index]).name;
        }
        
        -(int)rowAtIndex:(int)index {
            return ((MyObject *)_backendArray[index]).row;
        }
        
        -(int)columnAtIndex:(int)index {
            return ((MyObject *)_backendArray[index]).column;
        }
        
        @end
        

        然后像这样使用:

        MyCustomArrayClass *customArray = [[MyCustomArrayClass alloc] init];
        
        // Your custom method
        int row = [customArray rowAtIndex:10];
        
        // NSMutableArray method
        [customArray removeLastObject];
        
        // Your custom class used just like an array !!!
        index = 20;
        MyObject *obj = customArray[index];
        

        这一切都很好,很干净,实际上很酷,可以实现和使用。

        希望对你有帮助。

        【讨论】:

          【解决方案5】:

          我必须同意 node ninja 和 PeyloW,因为从技术上讲,他们都有权利。实际上,这对我没有多大帮助。

          序言: 代码中有许多数组,它们全部对一仅包含一种但不同类型的数据,例如A 类、B 类、C 类。

          问题: 我可以通过传递错误的数组来轻松混合数组,例如一些选择器,因为它们都是 NSMutableArray。没有静态检查,只有运行时检查。

          解决方案 - 第一次尝试: 创建 NSMutableArray 的子类,以便编译器进行静态检查并警告错误的数据类型。

          这很好,因为即使您将错误的类型传递给 -addObject 或 -objectAtIndex,当您重载这些类型时,编译器也会发出警告。 这很糟糕,因为您不能以这种方式实例化 NSMutableArray 超类。

          解决方案 - 第二次尝试: 制作某种类型的新(代理)类,例如NSObject 与 NSMutableArray 一样,并添加 NSMutableArray 类型的类成员。

          这很好,因为您可以实例化 NSMutableClass,并且当您将错误的类型传递给 -addObject 或 -objectAtIndex 时,您可以在重载这些类型时进行编译器检查。

          不好的一面是你需要重载你使用的 NSMutableArray 的每个选择器,而不仅仅是那些在数组包含的类中不同的选择器。

          结论: 当你构建一些复杂的代码时,它的数组中有许多类类型,相信我值得一试。简单地通过这个编译器向我展示了几个我无法识别的错误,直到我在运行时面对它。或者更糟糕的是,最终用户会面对它。

          【讨论】:

            【解决方案6】:

            来自 Apple 对 NSArray 的参考,在 Methods to Override 部分:

            NSArray 的任何子类都必须覆盖原始实例方法 count 和 objectAtIndex:。这些方法必须在您为集合元素提供的后备存储上运行。对于这个后备存储,您可以使用静态数组、标准 NSArray 对象或其他一些数据类型或机制。您还可以选择部分或全部覆盖您想要为其提供替代实现的任何其他 NSArray 方法。

            附带说明一下,在 Objective-C 中,没有实际功能允许您将类声明为抽象类,就其本身而言,例如在 Java 中。因此,他们所做的是从他们想要强制被子类覆盖的某个方法中调用类似下面的代码。实际上,它们赋予类“抽象类”语义。

            这个方法定义充当一个抽象方法,如果没有被覆盖,它会引发一个异常,输出如下:

            -someAbstractFooMethod 仅为抽象类定义。定义 -[YourClassName someAbstractFooMethod]!

            - (void) someAbstractFooMethod
            {
                //Force subclassers to override this method
                NSString *methodName = NSStringFromSelector(_cmd);
                NSString *className = [self className];
                [NSException raise:NSInvalidArgumentException
                            format:@"-%@ only defined for abstract class. Define -[%@ %@]!", methodName, className, methodName];
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-12
              • 2021-10-14
              • 2013-09-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多