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

StackOverflow https://stackoverflow.com/questions/1829028

  •  11-09-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

Не нужно сообщать об ошибке — их уже несколько.


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

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

+ (instancetype) array;

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top