Зачем использовать (id) в сигнатуре метода, когда (NSObject *) будет более точным?

Всякий раз, когда я реализую метод в своем собственном коде, который может принимать или возвращать объекты более чем одного класса, я всегда стараюсь использовать наиболее подходящий суперкласс. Например, если бы я собирался реализовать метод, который мог бы вернуть NSArray * или NSDictionary * в зависимости от его ввода, я бы дал этому методу возвращаемый тип NSObject *, так как это самый обычный общий суперкласс. Вот пример:

@interface MyParser()
- (BOOL)stringExpressesKeyValuePairs:(NSString *)string;
- (BOOL)stringExpressesAListOfEntities:(NSString *)string;
- (NSArray *)parseArrayFromString:(NSString *)string;
- (NSDictionary *)parseDictionaryFromString:(NSString *)string;
@end

@implementation MyParser
- (NSObject *)parseString:(NSString *)string {
    if ([self stringExpressesKeyValuePairs:string]) {
        return [self parseDictionaryFromString:string];
    }
    else if ([self stringExpressesAListOfEntities:string]) {
        return [self parseArrayFromString:string];
    }
}
// etc...
@end

Я заметил много случаев в Foundation и других API, где Apple использует (id) в определенных сигнатурах методов, когда (NSObject *) будет более точным. Например, здесь метод NSPropertyListSerialization:

+ (id)propertyListFromData:(NSData *)data 
          mutabilityOption:(NSPropertyListMutabilityOptions)opt 
                    format:(NSPropertyListFormat *)format 
          errorDescription:(NSString **)errorString

Возможными типами возврата из этого метода являются NSData, NSString, NSArray, NSDictionary, NSDate и NSNumber. Мне кажется, что тип возврата (NSObject *) был бы лучшим выбором, чем (id), поскольку вызывающий пользователь мог бы вызвать методы NSObject, такие как сохранение без литья типов.

Обычно я пытаюсь подражать идиомам, установленным официальными структурами, но мне также нравится понимать, что их мотивирует. Я уверен, что у Apple есть веские основания использовать (id) в таких случаях, но я просто не вижу этого. Что мне не хватает?

Ответ 1

Использование id сообщает компилятору, что это будет объект неизвестного типа. Используя NSObject, компилятор будет ожидать, что вы будете использовать только сообщения, доступные NSObject. Итак... Если вы знаете, что массив был возвращен и он был введен как id, вы можете вызвать objectAtIndex: без предупреждений компилятора. Если вы возвращаетесь с помощью NSObject, вы получите предупреждения.

Ответ 2

Причина, по которой (id) используется в объявлениях метода, имеет два раза:

(1) Метод может принимать или возвращать любой тип. NSArray содержит любой случайный объект и, таким образом, objectAtIndex: возвращает объект любого случайного типа. Приведение его к NSObject* или id <NSObject> было бы неверным по двум причинам; во-первых, массив может содержать подклассы non NSObject, если они реализуют определенный небольшой набор методов и, во-вторых, для конкретного типа возврата требуется кастинг.

(2) Objective-C не поддерживает ковариантные объявления. Рассмотрим:

@interface NSArray:NSObject
+ (id) array;
@end

Теперь вы можете вызвать +array на NSArray и NSMutableArray. Первый возвращает неизменяемый массив, а второй - изменяемый массив. Из-за Objective-C отсутствия поддержки ковариантной декларации, если выше было объявлено как возвращающее (NSArray*), клиенты метода подклассов должны были бы сбрасываться в `(NSMutableArray *). Уродливые, хрупкие и подверженные ошибкам. Таким образом, использование типичного типа, как правило, является самым простым решением.

Итак... если вы объявляете метод, который возвращает экземпляр определенного класса, явно указывается. Если вы объявляете метод, который будет переопределен и что переопределение может вернуть подкласс, а тот факт, что он возвращает подкласс, будет открыт для клиентов, а затем используйте (id).

Не нужно записывать ошибку - уже есть несколько.


Обратите внимание, что теперь ObjC имеет ограниченную поддержку коразмерности с помощью ключевого слова instancetype.

т.е. NSArray + метод массива теперь может быть объявлен как:

+ (instancetype) array;

И компилятор будет рассматривать [NSMutableArray array] как возвращающий NSMutableArray*, а [NSArray array] будет считаться возвращающим NSArray*.

Ответ 3

Вы уже можете позвонить - указатель на идентификаторы типа без кастинга. Если вы используете определенный тип суперкласса, вам придется указывать указатель каждый раз, когда вы вызываете метод подкласса, чтобы избежать предупреждений компилятора. Используйте идентификатор, чтобы компилятор не предупредил вас и лучше обозначил ваши намерения.

Ответ 4

(id) также часто возвращается для облегчения подклассов объектов. Например, в методах инициализации и удобства return (id) означает, что любой подкласс не должен переопределять методы суперкласса, если для этого не существует конкретной причины.