2

我用一些使用类别的便捷方法扩展了 NSString 和 NSMutableString。这些添加的方法具有相同的名称,但具有不同的实现。例如,我已经实现了 ruby​​ 的“strip”函数,该函数删除了端点处的空格字符,但对于 NSString,它返回一个新字符串,对于 NSMutableString,它使用“deleteCharactersInRange”剥离现有字符串并返回它(如红宝石带!)。

这是典型的标题:

@interface NSString (Extensions)
-(NSString *)strip;
@end

@interface NSMutableString (Extensions)
-(void)strip;
@end

问题是当我声明 NSString *s 并运行 [s strip] 时,它会尝试运行 NSMutableString 版本并引发扩展。

NSString *s = @"   This is a simple string    ";
NSLog([s strip]);

失败:

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“尝试使用 deleteCharactersInRange 改变不可变对象:”

4

3 回答 3

4
于 2009-08-07T01:04:48.030 回答
1

一个例子将使这个问题更容易理解:

@interface Foo : NSObject {
}
- (NSString *)method;
@end

@interface Bar : Foo {
}
- (void)method;
@end

void MyFunction(void) {
    Foo *foo = [[[Bar alloc] init] autorelease];
    NSString *string = [foo method];
}

在上面的代码中,会分配一个“Bar”的实例,但是被调用者(MyFunction 中的代码)通过类型 Foo 引用了该 Bar 对象,据被调用者所知,foo 实现了“name”以返回一个细绳。然而,由于 foo 实际上是 bar 的一个实例,它不会返回一个字符串。

大多数时候,您不能安全地更改继承方法的返回类型或参数类型。有一些特殊的方法可以做到这一点。它们被称为协变和逆变。基本上,您可以将继承方法的返回类型更改为更强的类型,也可以将继承方法的参数类型更改为更弱的类型。这背后的原因是每个子类都必须满足其基类的接口。

因此,虽然将“方法”的返回类型从 NSString * 更改为 void 是不合法的,但将其从 NSString * 更改为 NSMutableString * 是合法的。

于 2009-08-07T04:19:08.657 回答
1

您遇到的问题的关键在于关于 Objective-C 中的多态性的一个微妙点。因为该语言不支持方法重载,所以假定方法名称唯一标识给定类中的方法。有一个隐含的(但重要的)假设,即被覆盖的方法与它所覆盖的方法具有相同的语义。

在您给出的情况下,可以说两种方法的语义不同;即,第一种方法返回一个新字符串,该字符串用接收者内容的“剥离”版本初始化,而第二种方法直接修改接收者的内容。这两个操作真的不等价。

我认为,如果您仔细研究一下 Apple 命名其 API 的方式,尤其是在 Foundation 中,它确实有助于阐明一些语义上的细微差别。例如,在 NSString 中有几种方法可以创建一个包含修改版本的接收器的新字符串,例如

- (NSString *)stringByAppendingFormat:(NSString *)format ...;

请注意,名称是一个名词,其中第一个单词描述返回值,名称的其余部分描述参数。现在将其与 NSMutableString 中的相应方法进行比较,以直接附加到接收器:

- (void)appendFormat:(NSString *)format ...;

相比之下,这个方法是一个动词,因为没有返回值来描述。因此,仅从方法名称就可以清楚地看出 -appendFormat: 作用于接收者,而 -stringByAppendingFormat: 不作用,而是返回一个新字符串。

(顺便说一句,NSString 中已经有一个方法至少可以完成您想要的部分功能:-stringByTrimmingCharactersInSet:。您可以将whitespaceCharacterSet其作为参数传递以修剪前导和尾随空格。)

因此,虽然一开始可能看起来很烦人,但我认为从长远来看,尝试效仿 Apple 的命名约定真的很值得。如果不出意外,它将有助于使您的代码更具自文档性,尤其是对于其他 Obj-C 开发人员。但我认为它也有助于澄清 Objective-C 和 Apple 框架的一些语义上的微妙之处。

另外,我同意类集群的内部细节可能令人不安,特别是因为它们对我们来说大多是不透明的。然而,事实仍然是 NSString 是一个类集群,它使用 NSCFString 来表示可变和不可变实例。因此,当您的第二个类别添加另一个-strip方法时,它会替换-strip第一个类别添加的方法。更改一个或两个方法的名称将消除此问题。

而且由于 NSString 中已经存在提供相同功能的方法,可以说您只需添加可变方法即可。理想情况下,它的名称应与现有方法相对应,因此应为:

- (void)trimCharactersInSet:(NSCharacterSet *)set
于 2010-03-04T18:35:48.587 回答