Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 303
altyus Avatar asked Feb 28 '14 12:02

altyus


1 Answers

Ok, so to answer this question, first we need to know what is the class cluster design pattern ?

From Apple's documentations:

Class clusters are a design pattern that the Foundation framework makes extensive use of. Class clusters group a number of private concrete subclasses under a public abstract superclass. The grouping of classes in this way simplifies the publicly visible architecture of an object-oriented framework without reducing its functional richness. Class clusters are based on the Abstract Factory design pattern.

So the super class will decide what type we will have for our newly created object

Now because these methods are shared between NSArray and NSMutableArray, the results could be different, then they return id, because we don't know what object will be returned.(mutableArray or immutableArray).

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

These methods return only NSArray if the message was sent to NSArray and NSMutableArray if the method was sent to NSMutableArray. Thats why they return instancetype

+ (instancetype)arrayWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (instancetype)arrayWithArray:(NSArray *)array;

- (instancetype)init;

Ok, so we said that the methods above return only instance type of the receiver. But what if we want arrayWithArray method to always return immutableArray no matter who is the receiver ?

That means NSMutableArray will get different type than instanceType, because NSArray is not of NSMutableArray type, in this case we would change the method to be like this:

// from
+ (instancetype)arrayWithArray:(NSArray *)array;
// to
+ (id)arrayWithArray:(NSArray *)array;

We say now return id, despite the object's type.

UPDATE:
Example similar to your code

NSString *filePath = [[NSBundle mainBundle]pathForResource:@"myFile" ofType:@"plist"];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
NSMutableDictionary *dict2 = [[NSMutableDictionary alloc] init];
NSLog(@"%@", NSStringFromClass([dict class]));  // prints __NSCFDictionary  // converted to immutable
NSLog(@"%@", NSStringFromClass([dict2 class])); // prints __NSDictionaryM, its mutable

[dict setObject:@"obj" forKey:@"key"];  // this will do nothing, because its immutable, we can't add new object

Here is what Apple say about using isKindOfClass: to check the mutability of class cluster

Be careful when using this method on objects represented by a class cluster. Because of the nature of class clusters, the object you get back may not always be the type you expected. If you call a method that returns a class cluster, the exact type returned by the method is the best indicator of what you can do with that object. For example, if a method returns a pointer to an NSArray object, you should not use this method to see if the array is mutable, as shown in the following code:

// DO NOT DO THIS! 
if ([myArray isKindOfClass:[NSMutableArray class]])
{
   // Modify the object 
}

Link: https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/isKindOfClass:

like image 175
Basheer_CAD Avatar answered Oct 05 '22 11:10

Basheer_CAD