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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With