局部 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)

这也是一种隔离代码的好办法。

results matching ""

    No results matching ""