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

时间:2014-02-28 12:45:05

标签: ios iphone objective-c dynamic-typing

查看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

我唯一可以提出这些特定方法的是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;

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

扩展一点我想在NSMutableDictionary上调用dictionaryWithContentsOfFile的返回类型

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;
} 

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

  

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

     

我可以改变字典

因此,我可以向字典中添加新的键和对象。

2 个答案:

答案 0 :(得分:5)

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

来自Apple的文档:

  

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

因此超级类将决定我们将为新创建的对象提供什么类型

现在因为这些方法在NSArrayNSMutableArray之间共享,结果可能不同,然后返回id,因为我们不知道将返回哪个对象。( mutableArray或immutableArray)。

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

如果邮件已发送到NSArray,则这些方法仅返回NSArray,如果将方法发送到NSMutableArray,则{}返回NSMutableArray。这就是为什么他们返回instancetype

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

- (instancetype)init;

好的,所以我们说上面的方法只返回接收者的实例类型。但是,如果我们希望arrayWithArray方法始终返回immutableArray而不管接收者是谁?

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

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

我们说现在返回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

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

  

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

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

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

答案 1 :(得分:1)

类集群可能会返回除您创建它们的类以外的类。 对于Foundation类来说,这通常是正确的,因为在许多情况下它们会创建某种优化类。该类仍将从isKindOfClass返回YES:

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