Null, Nil и пустота

Как мы можем представить программно «ничего»? Например, когда метод ожидает на вход объект, но его нет, а вызов происходит. В программах всё должно быть чётко и понятно, там нельзя сказать «Извините, ничего не пришло».
В языках программирования отсутствие значения – тоже значение! Это означает, что мы можем сравнивать данные с этим «ничем», чтобы проверить: нам на вход был подан объект или подано его отсутствие. Objective C в этом плане не является исключением, так давайте рассмотрим это подробнее.
Всё бы ничего, да всё ничего
Для начала вспомним, что Objective C является расширением языка С («объектный Си»). Всё, что есть в С, есть и в Objective C. В языке С, уж простите за каламбур, используются два значения для отсутствия значения.Во-первых, это «0» для простых скалярных типов (int, char и т.д.). А во-вторых, для указателей используется ключевое слово NULL, которое, по сути, являет собой тот же «0» только в контексте работы с указателем.
В самом «объектном Си» добавляется ключевое слово nil для указания на то, что «объект не существует». Не смотря на то, что семантически NULL и nil разные ключевые слова, фактически они выполняют одну и ту же роль.
А если подняться на уровень фреймворков?
Foundation определяет объект-синглтон NSNull, который представляет собой «NULL-объект», хотя, фактически, это полноценный объект, у которого есть адрес и размер (об этом чуть позже). Ну и наконец, в Foundation/NSObjCRuntime.h определяется Nil, который являет собой класс-указатель на «ничего».Немного запутанно, не так ли? Давайте разбираться!
Если с наследием С в виде 0/NULL всё более менее понятно, то на остальных вариантах «ничего» остановимся чуть подробнее.Каждый объект иерархии NSObject после вызова команды alloc инициализируется пустотой. Это значит, что каждое поле получает значение nil/0 в зависимости от типа (скаляр/объект). Поэтому нет необходимости делать это вручную, если какие-то поля требуется помечать как «пусто».
Есть ещё одна особенность nil – одновременно дар и проклятье: ему можно посылать сообщения, как обычному объекту, и это не приведёт к крашу приложения!
К примеру, такая конструкция вполне себе жизнеспособна:
NSNumber *asd = nil;
[asd stringValue];
Для сравнения: в языке С++ подобный вызов однозначно вызовет runtime exception. Зачастую это бывает удобно, поскольку избавляет от лишних проверок на существование объекта, когда в этом нет необходимости. Однако, то же самое вполне может вызвать серьёзные проблемы при отладке, например:
NSMutableDictionary *map = //may cause nil for some reason
....
[map setValue:value forKey:key];
Так как наш словарь пуст, не произойдёт ничего! И если вызовы и работа со словарём находятся в разных местах, иногда бывает довольно сложно понять, почему словарь пуст, хотя мы явно задаём его заполнение.
Выходов из этого довольно много, один из самых распространённых – использование assert наподобие NSParameterAssert для проверки критических частей кода на не-nil у входных параметров.
Рассмотрим класс NSNull
Для чего же он нужен? Представим ситуацию, когда нам нужно сохранить несколько объектов в коллекцию, например, в NSArray. Что произойдёт если часть объектов будет nil? Это вызовет ошибку времени исполнения, потому что массив не сможет определить, сколько памяти надо выделить под пустой (т.е. нулевой по памяти, в этом контексте) объект (а реализация NSArray, в конечном итоге, сводится к выделению памяти под объекты, которые он содержит).Чтобы избежать подобного, используется объект NSNull с единственным методом `+null`, который создаёт этот объект. Это объект-пустышка, показывающий, что реально никакого значения нет, но при этом не нарушается целостность коллекции, в которую его помещают.
Остаётся последнее ключевое слово Nil
Если nil используется для обозначения пустого объекта класса, то Nil используется для ссылки на пустой класс. Разницу можно увидеть из примера:NSString *a = nil;
Class b = Nil;
Но используется это очень редко. Нужно просто знать, что такое поведение допустимо.
Суммируем всё в виде таблицы:
Ключевое слово | Значение | Пояснение |
NULL | (void *)0 | Пустое значение для указателей языка С |
nil | (id)0 | Пустое значение для объектов Objective C |
Nil | (Class)0 | Пустое значение для классов Objective C |
NSNull | [NSNull null] | Пустой класс-представление языка Objective-C в виде объекта |