为什么Apple在类构造函数中对instancetype的使用不一致?

alt*_*yus 13 iphone objective-c dynamic-typing ios

看一下NSArray.h中的NSArray创建方法块.

返回id的方法是否有正当理由不返回instancetype?

Apple甚至努力添加内联注释,让我们知道id在这种情况下会返回一个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
Run Code Online (Sandbox Code Playgroud)

我能提出这些特殊方法的唯一方法就是Apple的这个指导

"由aURL标识的位置的数组表示必须只包含属性列表>对象(NSString,NSData,NSArray或NSDictionary对象).这个>数组包含的对象是不可变的,即使数组是可变的."

但是,这对我来说还没有解释id over instancetype的用法,因为它们仍然允许NSArray子类返回自己的 instancetype

NSDictionary遵循完全相同的模式,其中创建包含文件或URL内容的字典,并使用id所有其他创建方法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;
Run Code Online (Sandbox Code Playgroud)

我知道Apple只是在替换id基础类,instancetype但在单个类中使用它的模式不一致是我们自己使用的指导,还是他们只是没有完成他们开始工作的课程?

稍微扩展我想探索dictionaryWithContentsOfFile在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;
} 
Run Code Online (Sandbox Code Playgroud)

以下内容输出到我的控制台:

这是一个可变字典为什么id而不是instancetype?

我可以改变字典

因此,我能够将新的键和对象添加到字典中.

Bas*_*CAD 5

好的,所以要回答这个问题,首先我们需要知道什么是类集群设计模式

来自Apple的文档:

类集群是Foundation框架广泛使用的设计模式.类集群在公共抽象超类下组合了许多私有具体子类.以这种方式对类进行分组简化了面向对象框架的公开可见体系结构,而不会降低其功能丰富性.类集群基于抽象工厂设计模式.

所以超类将决定我们将为新创建的对象提供什么类型

现在因为这些方法在NSArray和之间共享NSMutableArray,结果可能不同,然后返回id,因为我们不知道将返回什么对象.(mutableArray或immutableArray).

+ (id /* NSArray * */)arrayWithContentsOfFile:(NSString *)path;
+ (id /* NSArray * */)arrayWithContentsOfURL:(NSURL *)url;
- (id /* NSArray * */)initWithContentsOfFile:(NSString *)path;
- (id /* NSArray * */)initWithContentsOfURL:(NSURL *)url;
Run Code Online (Sandbox Code Playgroud)

NSArray当消息发送到NSArray并且NSMutableArray方法已发送到时,这些方法才会返回NSMutableArray.这就是为什么他们回来了instancetype

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

- (instancetype)init;
Run Code Online (Sandbox Code Playgroud)

好的,所以我们说上面的方法只返回接收器的实例类型.但是,如果我们想要arrayWithArray方法总是返回,immutableArray无论谁是接收者?

这意味着NSMutableArray将获得与instanceType不同的类型,因为NSArray不是NSMutableArray类型,在这种情况下我们会将方法更改为:

// from
+ (instancetype)arrayWithArray:(NSArray *)array;
// to
+ (id)arrayWithArray:(NSArray *)array;
Run Code Online (Sandbox Code Playgroud)

我们说现在返回id,尽管对象的类型.

更新:
类似于您的代码的示例

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
Run Code Online (Sandbox Code Playgroud)

以下是Apple对使用的说法isKindOfClass:检查类集群的可变性

在类集群表示的对象上使用此方法时要小心.由于类集群的性质,您获得的对象可能并不总是您期望的类型.如果调用返回类集群的方法,则该方法返回的确切类型是您可以对该对象执行的操作的最佳指示.例如,如果方法返回指向NSArray对象的指针,则不应使用此方法来查看该数组是否可变,如以下代码所示:

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

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


uch*_*aka 1

类簇可能会返回一个类,而不是您创建它们的类。对于基础类来说通常是这样,因为它们在许多情况下会创建某种优化的类。该类仍将从 isKindOfClass 返回 YES:

此外,一些免费桥接类返回在 Foundation 和 Core Foundation 之间共享的类。NSCFString 就是一个例子。