C 系语言中在方法内部我们是可以任意添加成对的大括号 {}
来限定代码的作用范围的。这么做一般来说有两个好处,首先是超过作用域后里面的临时变量就将失效,这不仅可以使方法内的命名更加容易,也使得那些不被需要的引用的回收提前进行了,可以稍微提高一些代码的效率;另外,在合适的位置插入括号也利于方法的梳理,对于那些不太方便提取为一个单独方法,但是又应该和当前方法内的其他部分进行一些区分的代码,使用大括号可以将这样的结构进行一个相对自然的划分。
举一个不失一般性的例子,虽然我个人不太喜欢使用代码手写 UI,但钟情于这么做的人还是不在少数。如果我们要在 Objective-C 中用代码构建 UI 的话,我们一般会选择在 -loadView
里写一些类似这样的代码:
-(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
UILabel *titleLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 30, 20, 40)];
titleLabel.textColor = [UIColor redColor];
titleLabel.text = @"Title";
[view addSubview:titleLabel];
UILabel *textLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 80, 20, 40)];
textLabel.textColor = [UIColor redColor];
textLabel.text = @"Text";
[view addSubview:textLabel];
self.view = view;
}
在这里只添加了两个 view,就已经够让人心烦的了。真实的界面当然会比这个复杂很多,想想看如果有十来个 view 的话,这段代码会变成什么样子吧。我们需要考虑对各个子 view 的命名,以确保它们的意义明确。如果我们在上面的代码中把某个配置 textLabel
的代码写错成了 titleLabel
的话,编译器也不会给我们任何警告。这种 bug 是非常难以发现的,因此在类似这种一大堆代码但是又不太可能进行重用的时候,我更推荐使用局部 scope 将它们分隔开来。比如上面的代码建议加上括号重写为以下形式,这样至少编译器会提醒我们一些低级错误,我们也可能更专注于每个代码块:
-(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
{
UILabel *titleLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 30, 20, 40)];
titleLabel.textColor = [UIColor redColor];
titleLabel.text = @"Title";
[view addSubview:titleLabel];
}
{
UILabel *textLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 80, 20, 40)];
textLabel.textColor = [UIColor redColor];
textLabel.text = @"Text";
[view addSubview:textLabel];
}
self.view = view;
}
在 Swift 中,直接使用大括号的写法是不支持的,因为这和闭包的定义产生了冲突。如果我们想类似地使用局部 scope 来分隔代码的话,一个不错的选择是定义一个接受 ()->()
作为函数的全局方法,然后执行它:
func local(closure: ()->()) {
closure()
}
在使用时,可以利用尾随闭包的特性模拟局部 scope:
override func loadView() {
let view = UIView(frame: CGRectMake(0, 0, 320, 480))
local {
let titleLabel = UILabel(frame: CGRectMake(150, 30, 20, 40))
titleLabel.textColor = UIColor.redColor()
titleLabel.text = "Title"
view.addSubview(titleLabel)
}
local {
let textLabel = UILabel(frame: CGRectMake(150, 80, 20, 40))
textLabel.textColor = UIColor.redColor()
textLabel.text = "Text"
view.addSubview(textLabel)
}
self.view = view
}
不过在 Swift 2.0 中,为了处理异常,Apple 加入了 do
这个关键字来作为捕获异常的作用域。这一功能恰好为我们提供了一个完美的局部作用域,现在我们可以简单地使用 do
来分隔代码了:
do {
let textLabel = UILabel(frame: CGRectMake(150, 80, 20, 40))
//...
}
在 Objective-C 中还有一个很棒的技巧是使用 GNU C 的声明扩展来在限制局部作用域的时候同时进行赋值,运用得当的话,可以使代码更加紧凑和整洁。比如上面的 titleLabel
如果我们需要保留一个引用的话,在 Objective-C 中可以写为:
self.titleLabel = ({
UILabel *label = [[UILabel alloc]
initWithFrame:CGRectMake(150, 30, 20, 40)];
label.textColor = [UIColor redColor];
label.text = @"Title";
[view addSubview:label];
label;
});
Swift 里当然没有 GNU C 的扩展,但是使用匿名的闭包的话,写出类似的代码也不是难事:
titleLabel = {
let label = UILabel(frame: CGRectMake(150, 30, 20, 40))
label.textColor = UIColor.redColor()
label.text = "Title"
self.view.addSubview(label)
return label
}()
这也是一种隔离代码的很好的方式。