当 (NSObject *) 更精确时,为什么在方法签名中使用 (id) ?
每当我在自己的代码中实现一种可以接受或返回多个类的对象的方法时,我总是尝试使用最具体的可用超类。例如,如果我要实现一个可能根据其输入返回 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 中,Apple 在某些方法签名中使用 (id) ,而 (NSObject *) 会更精确。例如,下面是 NSPropertyListSerialization 的方法:
+ (id)propertyListFromData:(NSData *)data
mutabilityOption:(NSPropertyListMutabilityOptions)opt
format:(NSPropertyListFormat *)format
errorDescription:(NSString **)errorString
该方法可能的返回类型是 NSData、NSString、NSArray、NSDictionary、NSDate 和 NSNumber。在我看来,(NSObject *) 的返回类型将是比 (id) 更好的选择,因为调用者将能够调用 NSObject 方法,例如在没有类型转换的情况下保留。
我通常会尝试模仿官方框架建立的习惯用法,但我也喜欢了解它们的动机。我确信苹果在这种情况下使用 (id) 有一些正当的理由,但我只是没有看到它。我缺少什么?
Whenever I implement a method in my own code that can accept or return objects of more than one class, I always try to use the most specific superclass available. For example, if I were going to implement a method that might return an NSArray * or an NSDictionary * depending on its input, I would give that method a return type of NSObject *, since that's the most direct common superclass. Here's an example:
@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
I've noticed many cases in Foundation and other APIs where Apple uses (id) in certain method signatures when (NSObject *) would be more precise. For example, here's a method of NSPropertyListSerialization:
+ (id)propertyListFromData:(NSData *)data
mutabilityOption:(NSPropertyListMutabilityOptions)opt
format:(NSPropertyListFormat *)format
errorDescription:(NSString **)errorString
The possible return types from this method are NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber. It seems to me that a return type of (NSObject *) would be a better choice than (id), since the caller would then be able to call NSObject methods like retain without a type-cast.
I generally try to emulate the idioms established by the official frameworks, but I also like to understand what motivates them. I'm sure that Apple has some valid reason for using (id) in cases like this, but I'm just not seeing it. What am I missing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在方法声明中使用 (id) 的原因有两个:
(1) 该方法可以采用或返回任何类型。 NSArray 包含任何随机对象,因此,
objectAtIndex:
将返回任何随机类型的对象。将其转换为NSObject*
或id
是不正确的,原因有两个:首先,数组可以包含非 NSObject 子类,只要它们实现特定的一小组方法即可;其次,特定的返回类型需要进行强制转换。(2) Objective-C 不支持协变声明。考虑一下:
现在,您可以在
NSArray
和NSMutableArray
上调用+array
。前者返回一个不可变数组,后者返回一个可变数组。由于 Objective-C 缺乏协变声明支持,如果以上声明为返回(NSArray*)
,则子类方法的客户端必须强制转换为 `(NSMutableArray*)。丑陋、脆弱且容易出错。因此,使用泛型类型通常是最直接的解决方案。所以...如果您声明一个返回特定类实例的方法,请显式进行类型转换。如果您声明一个将被重写的方法,并且该重写可能会返回一个子类,并且它返回一个子类的事实将暴露给客户端,那么请使用
(id)
。无需提交错误 - 已经有几个错误了。请注意,ObjC 现在通过
instancetype
关键字提供有限的协方差支持。即
NSArray
的 +array 方法现在可以声明为:编译器会将
[NSMutableArray array]
视为返回NSMutableArray*
while < code>[NSArray array] 将被视为返回NSArray*
。The reason why (id) is used in method declarations is two fold:
(1) The method may take or return any type. NSArray contains any random object and, thus,
objectAtIndex:
will return an object of any random type. Casting it toNSObject*
orid <NSObject>
would be incorrect for two reasons; first, an Array can contain non NSObject subclasses as long as they implement a certain small set of methods and, secondly, a specific return type would require casting.(2) Objective-C doesn't support covariant declarations. Consider:
Now, you can call
+array
on bothNSArray
andNSMutableArray
. The former returns an immutable array and the latter a mutable array. Because of Objective-C's lack of covariant declaration support, if the above were declared as returning(NSArray*)
, clients of the subclasses method would have to cast to `(NSMutableArray*). Ugly, fragile, and error prone. Thus, using the generic type is, generally, the most straightforward solution.So... if you are declaring a method that returns an instance of a specific class, typecast explicitly. If you are declaring a method that will be overridden and that override may return a subclass and the fact that it returns a subclass will be exposed to clients, then use
(id)
.No need to file a bug -- there are several already.Note that ObjC now has limited co-variance support through the
instancetype
keyword.I.e.
NSArray
's +array method could now be declared as:And the compiler would treat
[NSMutableArray array]
as returning anNSMutableArray*
while[NSArray array]
would be considered as returningNSArray*
.使用 id 告诉编译器它将是一个未知类型的对象。使用 NSObject,编译器会期望您仅使用 NSObject 可用的消息。所以...如果您知道返回了一个数组并且将其转换为 id,则可以调用 objectAtIndex: 而不会出现编译器警告。当返回 NSObject 类型时,您会收到警告。
Using id tells the compiler it will be an object of unknown type. Using NSObject the compiler would then expect you to only be using messages available to NSObject. So... If you know an array was returned and it's casted as id, you can call objectAtIndex: without compiler warnings. Whereas returning with a cast of NSObject, you'll get warnings.
您已经可以在 id 类型的指针上调用 -retain 而不进行强制转换。如果您使用特定的超类类型,则每次调用子类的方法时都必须强制转换指针,以避免编译器警告。使用 id 这样编译器就不会警告您并更好地表明您的意图。
You can already call -retain on pointers of type id without casting. If you use a specific superclass type, you'll have to cast the pointer every time you call a subclass's method in order to avoid compiler warnings. Use id so the compiler won't warn you and to better signify your intent.
(id) 也经常被返回,以便更容易地对对象进行子类化。例如,在初始化程序和便捷方法中,返回 (id) 意味着任何子类都不必重写超类的方法,除非有特定原因这样做。
(id) is also often returned in order to enable objects to be subclassed more easily. For instance, in initializer and convenience methods, returning (id) means that any subclass doesn't have to override the superclass's methods unless there is a specific reason to do so.