- PVSM.RU - https://www.pvsm.ru -
Понятие отсутствия чего-либо — ничего — это не только философская, но и вполне улититарная единица: людям, как и созданным ими вычислительным машинам, часто приходится оперировать «пустыми», выражающими лишь несуществование чего-либо значениями, будь то отсутствие денег на банковской карте, 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
:
-capitalizedStringWithLocale:
класса NSString
принимает в качестве аргумента локаль (объект класса NSLocale
), либо nil
— в последнем случае при изменении регистра строки будет использоваться каноническая декомпозиция (NFD) [4] Unicode-символов независимо от настроек локали на компьютере пользователя (то есть, данный метод будет эквивалентен методу -capitalizedString
).
- (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
в результате работы парсера.
Непоследок сводная таблица:
Обозначение | Легким движением руки… | Поснение |
---|---|---|
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/
Нажмите здесь для печати.