NULL、nil、Nil…
在Objective-C 语言中,有很多个代表「没有东西」的东西,一开始也很容易搞混。包括:
- NULL
- nil
- Nil
- NSNull
- NSNotFound
NULL
NULL 其实并不算是Objective-C 的东西,而是属于C 语言。NULL 就是C 语言当中的空指标,是指向0 的指标。绝大多数状况下,nil、Nil 与NULL 可以代替使用,但是语意上,当某个API 想要你传入某个指标(void *)时,而不是id 型别时,虽然你可以在这种状况下传入Objective-C 实例指针,也就是可以传入nil,但是传入NULL 意义会比较清楚。
像建立NSTimer 时,API 在userInfo 这边要求的是id,我们传入nil 会比较好:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
而像UIView的beginAnimations:context
的定义是:
+ (void)beginAnimations:(NSString *)animationID context:(void *)context;
在这边,传入NULL 就会比传入nil 好。
nil
nil 是空的Objective-C 实例指针,也一样是指向0。如果我们建立了一个Objective-C 实例的变量,当我们不想要使用这个物件的时候,便可以将这个变量指向nil;我们可以对nil 调用任何的Objective-C method,都不会产生问题。
我们需要注意在Array与Dictionary中使用nil的状况。在使用NSArray的-arrayWithObjects:
或NSDictionary的-dictionaryWithObjectsAndKeys:
这些被标为NS_REQUIRES_NIL_TERMINATION
的method的时候,nil会被当成是最后一个参数,出现在nil之后的参数都会被忽略,而且我们在传入参数的时候,最后一个参数也一定要是nil。
比方说,我们写一段这样的程式:
NSArray *a = [NSArray arrayWithObjects:@1, @2, nil, @3];
NSLog(@"a:%@", a);
这个array就只会有@1与@2,@3就会被截掉。主要原因是,这类method大概都是这样实作的:首先使用一个va_list读取所有传入的参数,然后用回圈呼叫va_arg
,只要遇到nil就停止回圈,像下面这段程式:
va_list list;
va_start(list, arg);
do {
if (arg == nil) {
break;
}
NSLog(@"arg:%@", arg);
} while ((arg = va_arg(list, id)));
va_end(list);
}
另外,当我们对NSMutableArray 插入nil,或使用Xcode 4.4 之后的literal 来写NSArray 或NSDictionary的时候,如果传入nil,也会发生exception 而造成crash。下面这段code一定会crash。
NSMutableArray *a = [[NSMutableArray alloc] init];
[a addObject:(id)nil];
NSArray *a = @[(id)nil];
NSArray *a = @[(id)nil];
Nil
nil 是空的instance,而开头大写的Nil 则是指空的class。比方说,当我们想要判断某个Class 是不是空的,语意上应该用Nil 而不是nil。
我们其实不常判断一个Class 是不是Nil。比较有可能的场合,是为了处理向下相容,像某个Class 只在某一版的新OS 上存在,但我们还需要支持旧的OS,所以我们会在确定某个Class 不是Nil 的状况下,才执行某段程式码:
Class cls = NSClassFromString(@"Abcdefg");
if (cls != Nil) {
// Do something.
}
但如果我们去看,nil 与Nil 其实是一样的。
#ifndef Nil
# if __has_feature(cxx_nullptr)
# define Nil nullptr
# else
# define Nil __DARWIN_NULL
# endif
#endif
#ifndef nil
# if __has_feature(cxx_nullptr)
# define nil nullptr
# else
# define nil __DARWIN_NULL
# endif
#endif
NSNull
不同于NULL、nil与Nil,NSNull是确实存在的Objective-C实例。前面讲过,我们无法在array或dictionary中插入nil,但有的时候我们会需要用一个东西代表「没有东西」,就会使用[NSNull null]
这个实例。
假如我们拿到一个JSON 文件,然后透过NSJSONSerialization,把JSON 档案转换成Objective-C 实例,在这个实例中,JSON dictionary 会转成NSDictionary,array 会转成NSArray,字串与数字分别会转换成NSString 与NSNumber,而JSON 里头的null 则会转成NSNull 实例。
NSNotFound
NSNotFound所代表的是「找不到这个东西的index」。比方说,我们有一个array是@[@1, @2, @3]
,当我们想要问在这个array中,@4
是第几笔资料的时候(呼叫indexOfObject:
),因为@4
并不在这个array里头,所以会回传NSNotFound,表示我们没有在array里头找到想要的东西。
同样的,如果我们在一个字串里头找不到某一段片段,像我们想在@"KKBOX"
这个字串里头找某@"a"
,呼叫rangeOfString:
的结果([@"KKBOX" rangeOfString:@"a"]
)是一个NSRange,这个range的location也一样是NSNotFound。
NSNotFound 是整数的最大值,我们通常不会建立这么大的array,所以用最大的整数代表找不到。我们要注意,在64 位元与32 位元下的的整数最大值是不一样的,所以在64 位元与32 位元下,NSNotFound 代表的是不同的数字,64 位元下是9223372036854775807 (2的63 次方减一),32 位元下是2147483647(2 的31 次方减一)。
所以下面这段程式在32 位元下正常,但是64 位元环境下就有问题。
#ifndef Nil
# if __has_feature(cxx_nullptr)
# define Nil nullptr
# else
# define Nil __DARWIN_NULL
# endif
#endif
#ifndef nil
# if __has_feature(cxx_nullptr)
# define nil nullptr
# else
# define nil __DARWIN_NULL
# endif
#endif
在32 位环境下,我们用的是32 位整数,所以x 会等于NSNotFound;在64 位元环境下,NSNotFound 是64 位元整数最大值,但x 被cast 成32 位整数,所以x就无法等于NSNotFound 了。
WebUndefined
在Mac OS X 上,我们还有可能遇到另外一种代表「没有东西」的物件,便是WebUndefined。在Mac 上使用WebView 的时候,如果我们让WebView里头的JavaScript 来呼叫Objective-C 或Swift 的native 实现,那么,在JavaScript 里头的undefined 传到Objective-C 或Swift 的method 里头时,就会变成WebUndefined实例。
相关说明可以参阅JavaScript与Objective-C的沟通这一节。