为什么在(NSObject *)更精确的时候在方法签名中使用(id)?

时间:2009-12-01 21:24:42

标签: objective-c nsobject

每当我在自己的代码中实现一个可以接受或返回多个类的对象的方法时,我总是尝试使用最具体的超类。例如,如果我要实现一个可能根据其输入返回NSArray *或NSDictionary *的方法,我会给该方法一个返回类型的NSObject *,因为那是最直接的常见超类。这是一个例子:

@interface MyParser()
- (BOOL)stringExpressesKeyValuePairs:(NSString *)string;
- (BOOL)stringExpressesAListOfEntities:(NSString *)string;
- (NSArray *)parseArrayFromString:(NSString *)string;
- (NSDictionary *)parseDictionaryFromString:(NSString *)string;
@end

@implementation MyParser
- (NSObject *)parseString:(NSString *)string {
    if ([self stringExpressesKeyValuePairs:string]) {
        return [self parseDictionaryFromString:string];
    }
    else if ([self stringExpressesAListOfEntities:string]) {
        return [self parseArrayFromString:string];
    }
}
// etc...
@end

我注意到在Foundation和其他API中有许多案例,当(NSObject *)更准确时,Apple在某些方法签名中使用(id)。例如,这是NSPropertyListSerialization的方法:

+ (id)propertyListFromData:(NSData *)data 
          mutabilityOption:(NSPropertyListMutabilityOptions)opt 
                    format:(NSPropertyListFormat *)format 
          errorDescription:(NSString **)errorString

此方法可能的返回类型是NSData,NSString,NSArray,NSDictionary,NSDate和NSNumber。在我看来,返回类型(NSObject *)将是一个比(id)更好的选择,因为调用者将能够在没有类型转换的情况下调用NSObject方法,例如retain。

我通常会尝试模仿官方框架所建立的习语,但我也想了解是什么激励了他们。我确信Apple在这种情况下使用(id)有一些正当理由,但我只是没有看到它。我错过了什么?

4 个答案:

答案 0 :(得分:19)

在方法声明中使用(id)的原因有两个:

(1)该方法可以采取或返回任何类型。 NSArray包含任何随机对象,因此objectAtIndex:将返回任意随机类型的对象。由于两个原因,将其投放到NSObject*id <NSObject>是不正确的;首先,一个Array可以包含非NSObject子类,只要它们实现一小组方法,其次,特定的返回类型需要转换。

(2)Objective-C不支持协变声明。考虑:

@interface NSArray:NSObject
+ (id) array;
@end

现在,您可以在+arrayNSArray上致电NSMutableArray。前者返回不可变数组,后者返回可变数组。由于Objective-C缺乏协变声明支持,如果上述声明为返回(NSArray*),则子类方法的客户端必须转换为`(NSMutableArray *)。丑陋,脆弱,容易出错。因此,通常使用泛型类型是最直接的解决方案。

所以......如果你声明一个返回特定类实例的方法,那么显式地进行类型转换。如果您声明一个将被覆盖的方法,并且该覆盖可能返回子类,则返回子类的事实将向客户端公开,然后使用(id)

无需提交错误 - 已经有几个。


请注意,ObjC现在通过instancetype关键字支持的协方差支持有限。

即。 NSArray的+数组方法现在可以声明为:

+ (instancetype) array;

编译器会将[NSMutableArray array]视为返回NSMutableArray*,而[NSArray array]将被视为返回NSArray*

答案 1 :(得分:8)

使用id告诉编译器它将是一个未知类型的对象。使用NSObject,编译器会希望您只使用NSObject可用的消息。所以...如果你知道一个数组被返回并且它被转换为id,你可以调用objectAtIndex:没有编译器警告。然后返回NSObject的演员阵容,你会收到警告。

答案 2 :(得分:1)

你可以在没有强制转换的情况下对id类型的指针调用-retain。如果使用特定的超类类型,则每次调用子类的方法时都必须强制转换指针,以避免编译器警告。使用id,这样编译器就不会警告你,也不会更好地表明你的意图。

答案 3 :(得分:1)

(id)也经常被返回,以便更容易地对对象进行子类化。例如,在初始化器和便捷方法中,返回(id)意味着除非有特定的原因,否则任何子类都不必覆盖超类的方法。