NSNull Category

相信不少开发者,都被NSNull坑过,最常见的是服务器返回的json里面,说好的字典、数组、数字,结果返回的是空值。

这个时候,NSJSONSerialization 会自动把他们换成 NSNull。当我们再去用dict[@“hello”]的时候,就会出触发exception,导致程序崩溃。

那么如何处理它呢?

我曾经的做法

写了个宏,判断返回的这个类是不是NSNull类,即 isKindOfClass

最简单的做法

相信大家都知道,[NSNull null] 并不是一个工厂方法,而是一个单例模式,那么我们直接去判断赋值的这个指针是不是[NSNull null] 就好了。

那么问题来了,编译器会多了一个warning,很烦人。

这篇文章里面介绍了各种做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void)someMethod
{
    NSString *aString = @"loremipsum";

    // This will complain: "Comparison of distinct pointer types ('NSString *' and 'NSNull *')"
    if (aString != [NSNull null])
    {

    }

    // This works (at least for strings), but isEqual: does different things 
    // for different classes, so it's not ideal
    if ([aString isEqual:[NSNull null]])
    {

    }

    // If you cast it to the class you're comparing against
    // then you're good to go
    if (aString != (NSString *)[NSNull null])
    {

    }

    // But we can also just cast it to id and
    // that works generically
    if (aString != (id)[NSNull null])
    {

    }

    // The thing that would be really cool,
    // would be [NSNull null] returning
    // id (like in the sample category below).
    // Wouldn't count on that one though.
    if (aString != [NSNull idNull])
    {

    }
}

这些都不是非常漂亮的解决方案,这篇文章的作者推荐:

1
2
3
4
5
6
@interface NSNull (idNull)
+ (id)idNull;
@end
@implementation NSNull (idNull)
+ (id)idNull { return [NSNull null]; }
@end

或者呢

1
2
3
4
if ([[NSNull null] isEqual:aString])
{

}

最终解决方案

上面的做法,都需要判断一次,还是很不优雅,为什么呢,我们还是不能像NULL,nil一样,直接拿来用,还是需要判断一下,这里推荐一套最漂亮的作法。

陈航提供了一个gist

我发现我的octopress的gist插件挂了,直接贴出来好了。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define NSNullObjects @[@"",@0,@{},@[]]

@interface NSNull (InternalNullExtention)
@end



@implementation NSNull (InternalNullExtention)


- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        for (NSObject *object in NSNullObjects) {
            signature = [object methodSignatureForSelector:selector];
            if (signature) {
                break;
            }
        }

    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL aSelector = [anInvocation selector];

    for (NSObject *object in NSNullObjects) {
        if ([object respondsToSelector:aSelector]) {
            [anInvocation invokeWithTarget:object];
            return;
        }
    }

    [self doesNotRecognizeSelector:aSelector];
}
@end

很高端霸气上档次的做法,通过处理异常情况,来实现这个功能。

这里还提供一个日本人的封装方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "NSNull+OVNatural.h"

@implementation NSNull (OVNatural)
- (void)forwardInvocation:(NSInvocation *)invocation
{
    if ([self respondsToSelector:[invocation selector]]) {
        [invocation invokeWithTarget:self];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *sig = [[NSNull class] instanceMethodSignatureForSelector:selector];
    if(sig == nil) {
        sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
    }
    return sig;
}

@end

关于[NSMethodSignature signatureWithObjCTypes:“@^vc”]的功能

可以参考以下两篇文章

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

http://nshipster.com/type-encodings/

Comments