Почему 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
}