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","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

相关文章

Swift 正式开源!Swift 团队很高兴宣布 Swift 开始开源新篇章。自从苹果发布 Swfit 编程语言,就成为了...
快,快,快!动动您的小手,分享给更多朋友! 苹果去年推出了全新的编程语言Swift,试图让iOS开发更简单...
开发者(KaiFaX) 面向开发者、程序员的专业平台! 和今年年初承诺的一样,苹果贴出了Swift语言的源码,...
本文由@Chun发表于Chun Tips :http://chun.tips/blog/2014/12/11/shi-yong-swift-gou-jian-zi-ding-yi...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...
本文由CocoaChina译者leon(社区ID)翻译 原文:THE RIGHT WAY TO WRITE A SINGLETON 在之前的帖子里聊过...