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

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

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

Ответы [ 4 ]

19 голосов
/ 02 декабря 2009

Причина, по которой (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 теперь может быть объявлен как:

+ (instancetype) array;

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

8 голосов
/ 02 декабря 2009

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

1 голос
/ 11 июня 2011

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

1 голос
/ 02 декабря 2009

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

...