Where things fall down 哪个地方的代码出问题了?
现在我们已经把空特性修饰符放进代码里面了,并且我们转换的 Swift 代码看起来也很棒,那我们来具体的看一看 Xcode 是如何在幕后使用这些信息的。
我真的强烈建议你把 Incorrect Uses of Nullable Values 功能打开,真的,看着我真诚的双眼。(;▽;)
我们把 fetchGreetingForTime 方法这样修改一下:
- (NSString*)fetchGreetingForTime:(NSString*)time {
self.name = nil;
return [NSString stringWithFormat:@"Good %@, %@!", time,
self.name];
}
显然在实际工作中,正常人绝对不可能犯这种错误。这里我要表达的情况是:如果我们给一个标记为 nonnull 的属性赋值为 nil,会发生什么呢?
我们来看 Xcode 的报错信息:"Null passed to a callee that requires a non-null argument." 这个提示的意思是:不能给标记为「不为空」的属性传一个空值。也就是说,当我们想这么做的时候,Xcode 拒绝我们把 nil 作为 setName 方法的参数。苹果自己所有的 API 都是用了空特性修饰符的,所以刚才的那个行为就好像,你,把 nil 作为给了 NSArray 的 addObject 参数,Xcode 当然不会编译这样的代码。
如果把这一行改成这样呢:
_name = nil;
这样写是能通过编译的。因为我们绕过了属性的方法,直接设置了实例变量的值 —— 这也进一步证明了我之前说的「不要随便直接去设置实例变量」。
现在我们再修改 fetchGreetingAtTime 方法:
- (NSString*)fetchGreetingForTime:(NSString*)time {
NSString *str = nil;
self.name = str;
return [NSString stringWithFormat:@"Good %@, %@!", time,
self.name];
}
这里将 nil 赋值给了一个临时创建的字符串 str,然后用 self. 的语法把 str 的值赋给属性的实例变量里。这个时候编译也可以通过,因为编译器无法从这两行里面辨认出 nil 到底是来自哪里。
由于这种情况的存在,我们就需要另一种工具了,这个工具叫做静态分析器(static analyzer)。和编译器相比,这个工具会更严格细致地检查代码是否存在逻辑错误,API 错误,或者是内存管理的问题。在上面这个情况下,他就会发现你首先把 nil 值赋值给了 str,然后把 self.name 的值定义成str。
现在我们点击菜单栏的 Product,然后选择 Analyze,你会看到一个新的蓝色的提示,这个颜色是用来显示静态分析器的错误提示的,他会报这样一个错误:"Null passed to a callee that requires a non-null 1st parameter."。这个错误的意思是:调用这个方法的时候,第一个参数不能为空,但是你传递了一个空值过来。而这正是这个代码的问题所在。静态分析器真的超棒的,它会在发现绕过编译器的错误,如果你想给声明为 nonnull 的实例变量直接赋 nil 值,哪怕你绕过了属性,他也是会发现的。
前面我说过,「你要仔细地解决你的代码」,这就是前面这句话的原因。如果说你没有仔细检查内部代码的话,空特性修饰符就无法守卫代码接口,它也没有存在的意义。这里你可以用静态分析器来帮你审阅代码,保证你所用的空特性修饰符如你所愿。
当你开始习惯于让静态分析器来帮你检查代码的时候,你可能希望它在每一次运行代码的时候都能够帮到你。那你就可以用 Cmd+B 或 Cmd+R 快捷键,在你编译运行程序的时候启动分析器。你也可以在 build settings 里找到 analysis,把 Analyze During Build 开起来,如果你想进一步了解后台发生的所有事情,你可以把 Mode of Analysis for Build 也打开。