Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use (id) in a method signature when (NSObject *) would be more precise?

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?

like image 792
cduhn Avatar asked Dec 01 '09 21:12

cduhn


2 Answers

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 to NSObject* or id <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:

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

Now, you can call +array on both NSArray and NSMutableArray. 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:

+ (instancetype) array;

And the compiler would treat [NSMutableArray array] as returning an NSMutableArray* while [NSArray array] would be considered as returning NSArray*.

like image 96
bbum Avatar answered Oct 11 '22 06:10

bbum


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.

like image 45
Brad G Avatar answered Oct 11 '22 04:10

Brad G