Search code examples
iosiphoneobjective-cdynamic-typing

Why is Apple inconsistent in its own usage of instancetype in class constructors?


Having a look at the block of NSArray creation methods in NSArray.h.

Is there a legitimate reason for the methods that are returning id to not return instancetype?

Apple even went through the effort of adding inline comments to let us know that id in this case returns an NSArray.

@interface NSArray (NSArrayCreation)

+ (instancetype)array;
+ (instancetype)arrayWithObject:(id)anObject;
+ (instancetype)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt;
+ (instancetype)arrayWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (instancetype)arrayWithArray:(NSArray *)array;

- (instancetype)init;   /* designated initializer */
- (instancetype)initWithObjects:(const id [])objects count:(NSUInteger)cnt; /* designated   initializer */

- (instancetype)initWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
- (instancetype)initWithArray:(NSArray *)array;
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;

+ (id /* NSArray * */)arrayWithContentsOfFile:(NSString *)path;
+ (id /* NSArray * */)arrayWithContentsOfURL:(NSURL *)url;
- (id /* NSArray * */)initWithContentsOfFile:(NSString *)path;
- (id /* NSArray * */)initWithContentsOfURL:(NSURL *)url;

@end

The only thing I could come up with these particular methods was this guidance from Apple

"The array representation at the location identified by aURL must contain only property list >objects (NSString, NSData, NSArray, or NSDictionary objects). The objects contained by this >array are immutable, even if the array is mutable."

However, this still to me doesn't explain the use of id over instancetype as they are still allowing NSArray sublclasses to return their own instancetype

NSDictionary follows the exact same pattern, where creating a dictionary with the contents of a file or URL uses id and all other creation methods use instancetype

- (instancetype)initWithObjectsAndKeys:(id)firstObject, ... NS_REQUIRES_NIL_TERMINATION;
- (instancetype)initWithDictionary:(NSDictionary *)otherDictionary;
- (instancetype)initWithDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL)flag;
- (instancetype)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys;

+ (id /* NSDictionary * */)dictionaryWithContentsOfFile:(NSString *)path;
+ (id /* NSDictionary * */)dictionaryWithContentsOfURL:(NSURL *)url;
- (id /* NSDictionary * */)initWithContentsOfFile:(NSString *)path;
- (id /* NSDictionary * */)initWithContentsOfURL:(NSURL *)url;

I am aware that Apple is just getting around to replacing id in foundation classes to instancetype but do the patterned inconsistencies in its usage within single classes act as guidance towards our own usage, or did they just not get around to finishing classes that they began working on?

to expand just a bit I wanted to explore the return type of dictionaryWithContentsOfFile when called on NSMutableDictionary

NSString * plistPath = [[NSBundle mainBundle] pathForResource:@"myFile" ofType:@"plist"];
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath]; 
    if ([ myDictionary isKindOfClass:[NSMutableDictionary class]])
    {
        NSLog(@"This is a mutable dictionary why id and not instancetype?");
        [myDictionary setObject:@"I can mutate the dictionary" forKey:@"newKey"];
    }
NSLog (@"%@", myDictionary[@"newKey"]); 
    return YES;
} 

The following was output to my console:

This is a mutable dictionary why id and not instancetype?

I can mutate the dictionary

Therefore, I am able to add new keys and objects to the dictionary.


Solution

  • Class clusters may return a class other than the class you create them from. This is generally true with Foundation classes as they will create some kind of optimized class in many cases. That class will still return YES from isKindOfClass:

    Also some toll free bridged classes return a class that is shared between Foundation and Core Foundation. One example is NSCFString.