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的沟通这一节。

results matching ""

    No results matching ""