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