В Objective C, почему я могу назначить NSArray для NSMutableArray без ошибки или предупреждения?

Меня беспокоит странное поведение, проиллюстрированное следующим примером:

NSMutableArray *a1 = [[NSMutableArray alloc] init]; // fine
NSMutableArray *a2 = [NSMutableArray array];        // fine, too

// compiler reports incompatible pointer types; good:
NSMutableArray *a3 = [[NSArray alloc] init]; 

// compiler says nothing and is happy to assign this!
NSMutableArray *a4 = [NSArray array]; 

Оба метода init и array обоих классов NSArray и NSMutableArray возвращают id. Однако поведение, когда я называю эти методы, просто не то же самое, и clang позволяет мне с радостью назначить пустую переменную NSArray переменной NSMutableArray!

Оказывается, что clang автоматически изменяет тип возвращаемого значения некоторых методов, включая семейство init, на instancetype и таким образом, во время компиляции можно определить, что [[NSArray alloc] init] возвращает NSArray *, а не NSMutableArray *. Но эта проверка просто не работает с методом array.

Почему? Должны ли строки, подобные моему последнему примеру, генерировать хотя бы предупреждение? Почему не все эти методы объявлены как возвращающие instancetype? Будет ли это изменяться в будущем?


Обновление

Хорошие новости: с iOS 7, [NSArray array] возвращает instancetype, поэтому присвоение a4 выше также дает предупреждение. Другие методы, такие как arrayWithContentsOfFile: или arrayWithContentsOfURL, возвращают id, хотя...

Ответ 1

Но эта проверка просто не работает с методом массива. Почему?

Как указано в документе, который вы связали, это потому, что -array не дает признанного типа связанных результатов. ObjC очень динамичен - компилятор не может гарантировать тип результата +array. Он делает это предположение с помощью некоторых методов, потому что соглашения об именах хорошо определены (например, +alloc, -init, +new, -self и т.д.). Таким образом, эта реализация просто прибегает к соглашениям об именах.

Компилятор также проверяет некоторые соглашения об именах в областях, которые вы не можете ожидать:

@implementation NSArray (DEMO)

- (id)initStr
{
    return [NSString new]; // << warning. RE: init prefix
}

@end

Должны ли строки, подобные моему последнему примеру, генерировать хотя бы предупреждение? Почему не все эти методы объявлены как возвращаемые instancetype? Будет ли это изменяться в будущем?

instancetype был введен примерно год назад (по внешнему виду). Некоторые из API были написаны несколько десятилетий назад. Я подозреваю, что это произойдет - вовремя - потому что (если используется правильно), это может указывать на множество проблем в существующем коде. Конечно, эти изменения нарушат существующие сборки (опять же, как правило, хорошие исправления, если они указаны в правильных местах).

Таким образом, файлы ошибок и дать инструменты и библиотеки несколько лет для обновления. Предполагая, что изменения сделаны, это, вероятно, произойдет при основном обновлении ОС.

Вероятно, было бы лучше, если бы он был включен как необязательное предупреждение в течение некоторого времени (в случае заголовков системы). Конечно, они могли бы использовать его с обратной совместимостью для старых компиляторов для новых API.

Кроме того, это изменение может быть легко модернизировано (не то, что более ранние компиляторы могли бы понять смысловую разницу между id и instancetype) простым typedef. Одна проблема с typedef заключается в том, что это глобальная декларация - компилятор может ограничивать атрибут word/modifier/для данной области, не вызывая боли при имитации ключевого слова, добавляя глобальный typedef. Apple GCC, возможно, никогда не поддерживает instancetype, поэтому логичный способ его внедрения для Apple GCC может быть глобальным typedef id, что может вызвать проблемы для некоторых людей (без семантической выгоды, если этот маршрут был взят), Обратите внимание, что аналогичные изменения были сделаны Apple в прошлом.

Ответ 2

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

NSMutableArray *a4 = [NSDictionary dictionary]; 

Это побочный эффект использования id, чтобы отказаться от безопасности типа, и, как вы заметили, это должно быть устаревшим поведением и заменено на instancetype (который выдает предупреждение о несовместимом типе при использовании описанным выше способом).

К сожалению, это не ошибка. instancetype является довольно новым ключевым словом, его принятие еще не широко распространено, и было бы смелым шагом начать использовать его в рамках Apple. Вы никогда не знаете, всегда есть надежда на следующий SDK!