Почему Apple не согласуется с собственным использованием instancetype в конструкторах классов?

Посмотрите на блок методов создания NSArray в NSArray.h.

Есть ли законная причина, по которой методы, возвращающие 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 над 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 но могут ли шаблонные несоответствия в его использовании в отдельных классах служить руководством для нашего собственного использования, или они просто не добрались до окончания классов, над которыми они начали работать?

чтобы немного расширить, я хотел исследовать возвращаемый тип 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;
} 

Следующее было выведено на мою консоль:

Это изменчивый словарь, почему id, а не instancetype?

Я могу изменить словарь

Поэтому я могу добавлять новые ключи и объекты в словарь.

2 ответа

Решение

Кластеры классов могут возвращать класс, отличный от класса, из которого вы их создали. Это в основном верно для базовых классов, поскольку во многих случаях они создают некий оптимизированный класс. Этот класс по-прежнему будет возвращать YES из isKindOfClass:

Также некоторые бесплатные мостовые классы возвращают класс, который используется совместно с Foundation и Core Foundation. Одним из примеров является NSCFString.

Итак, чтобы ответить на этот вопрос, сначала нам нужно знать, что такое шаблон проектирования кластера классов?

Из документации Apple:

Кластеры классов - это шаблон проектирования, который среда Foundation широко использует. Кластеры классов объединяют несколько частных конкретных подклассов в открытый абстрактный суперкласс. Таким образом, группирование классов упрощает общедоступную архитектуру объектно-ориентированной среды, не уменьшая ее функциональных возможностей. Кластеры классов основаны на шаблоне проектирования Abstract Factory.

Таким образом, суперкласс решит, какой тип будет у нас для вновь созданного объекта.

Теперь, потому что эти методы являются общими 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;

Эти методы возвращают только 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;

Мы говорим теперь вернуть идентификатор, несмотря на тип объекта.

ОБНОВИТЬ:
Пример похож на ваш код

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:

Другие вопросы по тегам