Почему NSArray arrayWithObjects требует конечного нуля?

Я понимаю, что он отмечает конец набора varargs, но почему он не может быть реализован таким образом, чтобы не требовать nil?

Ответы

Ответ 1

Все это связано с C-вызовом ABI.

Рассмотрим следующие методы:

- (id)initWithFormat:(NSString *)format, ...;
+ (id)arrayWithObjects:(id)firstObj, ...;
+ (id)dictionaryWithObjectsAndKeys:(id)firstObject, ...;

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

Теперь рассмотрим три метода. Все три имеют совершенно разные требования к тому, что может присутствовать в списке переменных аргументов. Массив должен быть связкой объектов, за которыми следует ноль. В словаре требуется куча пар объектов, за которыми следует нуль. Наконец, для метода string требуется куча аргументов, которые соответствуют типам в строке формата.

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

Нижняя строка: C ABI не имеет синтаксиса, который позволяет указать, что метод или функция принимает переменное число аргументов с любым набором ограничений на аргументы или их завершение.

Objective-C может изменять правила только для объявлений и invocations методов, но это не помогло бы с C-функциями или С++, оба из которых Objective-C должны оставаться совместимыми с.

Ответ 2

Любая функция varargs требует, чтобы кто-нибудь знал, сколько параметров присутствует - если вы не закончите нить, вам просто нужно что-то другое. В этом случае очевидной альтернативой является длина, но тогда вам нужно будет обновлять аргумент length каждый раз, когда вы изменили количество элементов в массиве, и это может быть громоздким или даже сломанным.

Я предполагаю, что у вас есть массив, который может содержать nil?

Ответ 3

@bbum дает отличные технические подробности. Вот несколько мыслей о практических "почему бы им не исправить это?"

Помните: C - это просто ассемблер, а Obj-C - это просто C... Конечно, его можно было бы перепроектировать, но практически нет давления. Вы все равно не могли поместить nil в массив (для этого потребовалось бы огромное изменение). Теперь компилятор предупреждает вас, если вы забыли нуль, так что разработчики не жалуются. Компромисс значительно упрощает язык (намного!) Проще, чем его род, получая преимущества десятилетий оптимизации C-компилятора и гарантированной совместимости с кодом C.

Одна вещь, которая должна стать ясной из обсуждения @bbum: NSArray не является языковой функцией Objective-C. C-массивы - это языковая функция, но NSArrays - это еще один объект, отличный от объектов, которые вы пишете.

Ответ 4

Существуют различные способы обхода, но мой текущий любимый вариант подходит для приложений больше, чем для выпущенных фреймворков. Примите NSArray в своем методе вместо "...", а затем заполните его макросом удобства ниже, который помещается в ваш файл префикса или заголовок утилиты.

#define $array (objs...) [NSArray arrayWithObjects: objs, nil]

Это позволит использовать несколько аргументов переменной длины с длинной меткой, и вы освободитесь от архаичного шаблона использования первого аргумента, а затем va_list и его братьев в пользу цикла for-in или многих других коллекций доступных инструментов.

[self findMatchingSetAndAlert: @ "title" ties: $array (tie1, tie2, tie3) shirts: $array (shirt1, shirt2, shirt3, shirt4)];

Если кто-то знает, как реализовать список, отличный от нуля, например stringWithFormat, сообщите нам об этом! Он использует атрибуты и макросы или что-то специально предназначенные для форматирования, но они каким-то образом реализованы.

Ответ 6

Простая причина в том, что за кулисами это цикл for, который будет продолжать принимать аргументы, от va_list до тех пор, пока не достигнет nil. Таким образом, конечным условием могло быть что угодно, например, строка "стоп". Но на самом деле nil довольно умный.

Пусть у нас есть три объекта hansel, gretel и woodcutter и создать массив из них:

NSArray *startCharacters = [NSArray arrayWithObjects:hansel, gretel, woodcutter, nil];

Теперь мы понимаем, что woodcutter никогда не был инициирован, поэтому он startCharacters nil, но startCharacters прежнему будет создаваться с hansel объектов hansel и gretel, поскольку, когда он достигает woodcutter он завершается. Таким образом, нулевое окончание в arrayWithObjects: предотвращает сбой приложения.

Если вам не нравится выписывать nil вы всегда можете создать массив следующим образом:

NSArray *startCharacters = @[hansel, gretel, woodcutter];

Это действительно, это короче, но это потерпит крах, если объект равен nil. Итак, вывод заключается в том, что arrayWithObjects: все еще может быть очень полезным, и вы можете использовать его в своих интересах.