Swift闭包(Closures)

闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。 在Swift中的闭包与C、OC中的blocks和其它编程语言(如Python)中的lambdas类似。

闭包可以捕获和存储上下文中定义的的任何常量和变量的引用。这就是所谓的变量和变量的自封闭, 因此命名为”闭包“(“Closures)”)。Swift还会处理所有捕获的引用的内存管理。

全局函数和嵌套函数其实就是特殊的闭包。 闭包的形式有三种:

  • 全局函数都是闭包,有名字但不能捕获任何值。
  • 嵌套函数都是闭包,且有名字,也能捕获封闭函数内的值。
  • 闭包表达式都是无名闭包,使用轻量级语法,可以根据上下文环境捕获值。

Swift中的闭包有很多优化的地方:

  • 根据上下文推断参数和返回值类型;
  • 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return);
  • 可以使用简化参数名,如0,1(从0开始,表示第i个参数…);
  • 提供了尾随闭包语法(Trailing closure syntax)。

闭包表达式

嵌套函数是非常强大的功能,在一个函数体内嵌套另一个函数。将函数作为参数和返回值也非常有用。这些都是一些特殊情况下的闭包。

闭包表达式是一种简短的、集中的语法。闭包表达式为了缩短代码以及优化代码的阅读性,提供了几种语法优化。这里使用数组的排序为大家展示闭包的优化。

Sort方法

// 函数做参数,排序
let names = ["阳君", "937447974", "a", "b", "c"]
func backwards(s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversed = names.sort(backwards)

从代码中可以看出,将函数作为参数传递对于代码的阅读性不是很好,这里就需要闭包表达式对其优化。

闭包语法

闭包表达式的结构图如下所示:

  • parameters:闭包接受的参数;
  • return type:闭包运行完毕的返回值;
  • statements:闭包内的运行代码。

下面运用闭包表达式代替backwards函数对sort进行优化。

reversed = names.sort({ (s1: String, s2: String) -> Bool in
    return s1 > s2
})

当要运行的代码很少时,你也可以将它们写在一行。

reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )

通过上下文判断类型

在闭包中,我们不必写参数的类型和返回值的类型,闭包可以通过上下文自动判断参数类型和返回值类型。

reversed = names.sort( { s1, s2 in return s1 > s2 } )

从单一表达式隐藏Return

在闭包中,如果运行的内容很少只有一行,则不必写return,闭包会自动返回。

reversed = names.sort( { s1, s2 in s1 > s2 } )

速记参数名称

在闭包中,我们不必命名参数名称。闭包中的参数可使用$去获得,第一个参数为$0,第二个为$1

reversed = names.sort( { $0 > $1 } )

算子函数

当在闭包中,只有一个表达式,做操作。如在sort中,只有两个参数做比较操作。闭包支持只输入>或<做比较。

reversed = names.sort(>)

尾随闭包

如果函数需要一个闭包作为参数,且这个参数是最后一个参数。我们又不想在()内写太多代码,我们可以运用尾随闭包。尾随闭包意味着闭包可以放在函数参数列表外,也就是括号外。

var reversed = names.sort() { $0 > $1 }

当尾随闭包中的参数只有一个时,我们可以省略()。

reversed = names.sort { $0 > $1 }

捕获值

闭包可以根据上下文环境捕获到定义的常量和变量。闭包可以引用和修改这些捕获到的常量和变量,就算在原来的范围内定义为常量或者变量已经不再存在。在Swift中闭包的最简单形式是嵌套函数。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)
print("(incrementByTen())") // prints "10"
print("(incrementByTen())") // prints "20"
print("(incrementByTen())") // prints "30"

上面的例子介绍了,有个函数makeIncrementer,在函数内有一个嵌套函数incremented。嵌套函数可以通过上下文使用它的外部值runningTotal和amount。

当你想声明另一个闭包类型时,可以像声明属性一样声明。

let incrementBySeven = makeIncrementer(forIncrement: 7)
print("(incrementBySeven())") //  prints "7"
print("(incrementByTen())")   //  prints "40"

可以看出两个闭包的引用是完全独立工作的。

闭包是引用类型

运用属性执行的闭包后,我们可以用另一个属性去引用,对于两个属性来说,它们是完全相同的,指向同一个闭包。

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() //  prints "50"

Noescape关键字

@noescape主要用于解决“保留环”问题如下所示,当你调用someFunctionWithEscapingClosure函数时,使用全局属性,会使用了self,这样你会发现每次调用闭包时,都会使用捕获self,这样容易造成内存泄露的问题,而且闭包中的操作其实是一成不变的,没有必要每次都访问。

Swift鉴于这种情况,希望在闭包内不使用self,因此产生了@noescape关键字。将@noescape写入闭包名前。这样在编写闭包内代码时,无须使用self属性,也避免了保留环问题。

var completionHandlers: [() -> Void] = []
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
    closure()
    // completionHandlers.append(closure) //会报错,closure无法被保存
}

func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
    completionHandler()
    completionHandlers.append(completionHandler)
}

class SomeClass {
    var x = 10
    func doSomething() {
        // 内存溢出,保留环问题
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNoescapeClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// prints "200"

completionHandlers.first?()
print(instance.x)
// prints "100"

Autoclosures关键字

在闭包中,我们调用函数时,是将代码封装为一个闭包传递给函数。如下所示:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
    print("Now serving (customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"

此时我们会思考,能否让代码直接为参数传递过去,也就是不用{}包含代码。@autoclosure关键字可以帮助你完成这种机制。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer2(@autoclosure customerProvider: () -> String) {
    print("Now serving (customerProvider())!")
}
// 闭包作为参数
serveCustomer2(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"

使用了@autoclosure默认也是使用@noescape,如果你只想使用autoclosure的特性,不想使用noescape的特性,你可以使用escape关键字,如下所示:

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []

//autoclosure和escaping一起用
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
    customerProviders.append(customerProvider)
}

// 添加闭包,并且闭包此时为参数
collectCustomerProviders(customersInLine.removeAtIndex(0))
collectCustomerProviders(customersInLine.removeAtIndex(0))

//循环使用闭包
for customerProvider in customerProviders {
    print("Now serving (customerProvider())!")
}
// prints "Now serving Barry!"
// prints "Now serving Daniella!"

其他

参考资料

The Swift Programming Language (Swift 2.1)

文档修改记录

时间 描述
2015-10-28 根据 The Swift Programming Language (Swift 2.1)中的Closures总结

版权所有:http://blog.csdn.net/y550918116j

文章导航