Подтвердить что ты не робот

Почему 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 sublclasses возвращать свои собственные 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?

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Из документации 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;

Мы говорим, что теперь возвращаем 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: