- PVSM.RU - https://www.pvsm.ru -

nil, Nil, NULL, NSNull. Всё — ничто

Понятие отсутствия чего-либо — ничего — это не только философская, но и вполне улититарная единица: людям, как и созданным ими вычислительным машинам, часто приходится оперировать «пустыми», выражающими лишь несуществование чего-либо значениями, будь то отсутствие денег на банковской карте, about:blank, чёрные дыры или grep "${rootpswrd}" /etc/passwd.

Для выражения подобного «несуществования» в языках программирования используется большое количество различных мнемоник. Мы же рассмотрим те из них, что используются в сверхпопулярном (за прошедшую пятилетку, но, будем верить, что и на долгие лета́ вперёд) языке Objective-C.

Унификация

В Objective-C существует несколько различных типов что ни на есть самого настоящего ничего. Причиной этому служит «многослойность» языка, объединяющая и процедурную парадигму языка Си, и объектно-ориентированность Smalltalk.
Си определяет ничто как 0 (ноль) для большинства базовых (примитивных) типов и как NULL исключительно для указателей.

На самом деле, в контексте указателей применим как NULL, так и 0, ввиду того что первый — не более чем макрос-обёртка для второго:

#define NULL ((void *)0)

Однако не следует использовать NULL в качестве замены 0 в тех местах, где ноль — алгебраическое значение:

int c = b*a;
if (c != NULL) {
    printf("Neither b nor a is equal to 0n");
}

Objective-C (как настройка на Си) сохранил все формы выражения ничего своего прародителя, добавив к ним ещё один: nil. nil это указатель на нулевой объект:

#if !defined(nil)
    #define nil (id)0
#endif

То есть, технически, он эквивалентен NULL.

Пожалуй, ни одна программа на Objective-C не обходится без использования фреймворка Foundation, который в свою очередь также привносит в язык ещё одно (уже четвертое по счёту) представление ничего — класс NSNull, имеющий один-единственный (не считая унаследованных) метод +null [1], который возвращает синглтон [2] класса NSNull.

Но и это ещё не всё: в дополнение к четырём вышеперечисленным мнемоникам, Foundation определяет макрос Nil (не путать с nil) — нулевой указатель типа Class:

#if !defined(Nil)
    #define Nil (Class)0
#endif

Пример использования Nil в реальной жизни придумать сложно: манипуляциями с классами в runtime [3] занимаются лишь немногие гики. Так или иначе, вот небольшой кусок кода, инициализирующий новый класс, унаследованный от NSString, с именем NSArray (да, это не очепятка, пояснение ниже) в рантайме:

#inlcude <objc/runtime.h>
	
Class foo = objc_allocateClassPair([NSString class], "NSArray", 0);
/* 
    Так как класс с именем NSArray уже существует, функция вернёт
    значение Nil — ошибка
*/
if (Nil != foo) {
    int check = class_addMethod(foo, @selector(makeWorldBetter), (IMP)_makeWorldBetter, "v@:"));
    if (check)
        objc_registerClassPair(foo);
}

Кое-что о nil

Рассмотрим пару случаев, когда программисту на Objective-C может понадобиться nil:

  1. Иногда разработчики той или иной функции (метода) допускают возможность, что не все входные параметры могут быть заданы пользователем. Например, метод -capitalizedStringWithLocale: класса NSString принимает в качестве аргумента локаль (объект класса NSLocale), либо nil — в последнем случае при изменении регистра строки будет использоваться каноническая декомпозиция (NFD) [4] Unicode-символов независимо от настроек локали на компьютере пользователя (то есть, данный метод будет эквивалентен методу -capitalizedString).
  2. Инициализация и «опустошение» объектов (например, для того, чтобы сообщить об ошибке):
    - (NSDictionary*)dictionaryForLicenseFile:(NSString *)path
    {
        NSData *licenseFile = [NSData dataWithContentsOfFile:path];
        /* 
            «Достаточно, вы мне плюнули в душу!» 
        */
        if (!licenseFile)
            return nil;
    			
        return [self dictionaryForLicenseData:licenseFile];
    }  	
    

    Следует, кстати, заметить, что распространённая среди разработчиков практика инициализации ivars [5] нулевыми значениями в методах семейства -init* не имеет смысла, так как их значения уже были приравнены к нолю ещё в методе -alloc [6]:

    … memory for all other instance variables is set to 0.

Одним из самых замечательных свойств ничего (то есть, nil) в Objective-C является возможность посылать ему совершенно любые сообщения, ответом на которые неизменно будет всё тот же nil:

    id foo = nil;
    int zero = [foo bar: @"Hello, Habr!"];

Часто эта особенность используется для сокращения количества проводимых проверок над объектами:

/* In a galaxy far, far away… */
if (foo != nil && [foo testValue: 0x90]) { … }
	
/* Программисты же из Млечного Пути могут писать просто */
if ([foo testValue: 0x31с040с3]) { … } 

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

   /* 
      «Да хоть NSPersistentStoreCoordinator» ©  Brad Cox & Tom Love
  */
  NSData *signature = [NSString stringWithFormat: @"%lu", 0xCAFEBABE];
  /*
      Исключение вылетит —  без @catch не поймаешь: никакого метода -bytes у класса NSString нет
   */
  bytes = [signature bytes];
И ладно бы этим промышляли только начинающие разработчики…

Несколько месяцев назад я обнаружил подобный баг в системе проверки лицензии приложения The Tagger [7], этот баг позволял любому пользователю (а не только тому, кто умеет работать напильником) обойти ограничение триальной версии, просто подсунув программе невалидный файл .lic (точнее .ttl).
Само собой разумеется, я уведомил об этом разработчика, но ответа так и не получил.
К слову, за код проверки отвечала Cocoa-версия популярного класса AquaticPrime [8] (которой, обычно, разработчики предпочитают CoreFoundation-версию).

К тому же, не стоит совсем забывать о проверках объектов на равенство nil: очень многие методы из стандартных фреймворков, да и (готов поспорить) некоторые ваши собственные, просто не расчитаны на работу с «нулевыми» объектами. Взять хотя бы +[NSArray arrayWithObjects:] — попробуйте создать массив из трёх объектов с nil в начале списка и посмотрите на результат. Ещё один пример: +[NSString stringWithString:], вызвав который с nil в качестве аргумента, получите исключение.

NSNull: ничто или нечто?

NSNull используется внутри фреймворка Foundation и некоторых других для того, чтобы обойти ограничение стандартных коллекций, вроде NSArray или NSDictionary, заключающееся в том, что они не могут содержать в себе значения nil.

NSNull — это своего рода обёртка над NULL и nil, позволяющая хранить их в объектах-коллекциях Objective-C.

Может быть не совсем понятно, зачем нужно хранить где-то нулевые объекты. Рассмотрим пример: в вашем iOS-приложении вам нужно распарсить некий JSON-файл, в котором есть кусок наподобие этого

{
 	"keys": ["X"], 
 	"command": "set_action_motion",
 	"args": {
	        "action": "vi_left_delete",
	        "motion": null
 	},
 	"context": [{"key": "setting.command_mode", "modes": [] }]
}

Скорее всего вы не будете разбирать JSON «руками», а воспользуетесь готовой библиотекой (вроде JSONKit [9]), которая в момент преобразует его в объекты стандартных классов Objective-C. Однако, значения argc["motion"] и context[0]["modes"] равны ничему, то есть null и пустому массиву соответственно! Эти-то значения и будут считаны в объекты класса NSNull в результате работы парсера.

Опять 11001b

Непоследок сводная таблица:

Обозначение Легким движением руки… Поснение
0 0 Ноль — он везде ноль
NULL (void *)0 Нулевой указатель в языке Си
nil (id)0 Нулевой указатель на объект Objective-C
Nil (Class)0 Нулевой указатель типа Class в Objective-C
NSNull [NSNull null] Синглтон класса NSNull — обёртки над nil и Null

Литература

P.S. я не стал оформлять этот топик как первод, потому как довольно сильно в итоге отошёл от оригинальной статьи. Надеюсь, в лучшую сторону.

Автор: ericbro

Источник [21]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ios-development/24246

Ссылки в тексте:

[1] +null: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNull_Class/Reference/Reference.html

[2] синглтон: http://ru.wikipedia.org/wiki/Одиночка_(шаблон_проектирования)

[3] runtime: http://habrahabr.ru/post/148922/

[4] каноническая декомпозиция (NFD): http://ru.wikipedia.org/wiki/Unicode#.D0.A4.D0.BE.D1.80.D0.BC.D1.8B_.D0.BD.D0.BE.D1.80.D0.BC.D0.B0.D0.BB.D0.B8.D0.B7.D0.B0.D1.86.D0.B8.D0.B8

[5] ivars: http://en.wikipedia.org/wiki/Instance_variable

[6] -alloc: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html

[7] The Tagger: http://deadbeatsw.com

[8] AquaticPrime: http://www.tempel.org/UsingAquaticPrime

[9] JSONKit: https://github.com/johnezang/JSONKit

[10] NSHipster: http://NSHipster.com

[11] nil / Nil / NULL / NSNull: http://nshipster.com/nil/

[12] Mattt Thompson: http://mattt.me/

[13] NSNull: https://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSNull_Class/Reference/Reference.html

[14] NSString: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/Reference.html

[15] NSObject: https://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html

[16] Programming with Objective-C: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html

[17] Objective-C Runtime Reference: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

[18] Objective-C Runtime Programming Guide: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html

[19] Юникод: http://ru.wikipedia.org/wiki/Юникод

[20] Objective-C: http://ru.wikipedia.org/wiki/Objective-C

[21] Источник: http://habrahabr.ru/post/165021/