/***************** 闭包 Closures ****************/
/*
* 闭包表达式 Closure Expressions
* 尾随闭包 Trailing Closures
* 值捕获 Capturing Values
* 闭包是引用类型 Closures Are Reference Types
* 非逃逸闭包 Nonescaping Closures
* 自动闭包 Autoclosures
*/
// 闭包是自包含的函数代码块,可以在代码中被传递和使用.
// 相当于C和OC的代码块block,以及其他编程语言的匿名函数
// 闭包可以捕获和存储上下文中任意常量和变量的引用,这就是所谓的闭合并包裹着这些常量和变量,俗称闭包.swift会为您管理在捕获过程中涉及到的所有内存操作
// 在函数中,全局和嵌套函数实际上也是特殊的闭包.
// 闭包采用如下三个方式
/*
* 全局函数是一个有名字但不会捕获任何值的闭包
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
* 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量活常量值的匿名闭包
*/
// swift的闭包表达式拥有简洁的风格,优化如下:
/*
* 利用上下文推断参数和返回值类型
* 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
* 参数名称缩写
* 尾随(Trailing)闭包语法
*/
// 闭包表达式 Closure Expressions
// 嵌套函数是一个在较复杂函数中方便惊醒命名和定义自包含代码模块的方式.
// 有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的
// 尤其是处理一些函数需要另外的函数作为该函数的参数的时候
// 闭包表达式是一种利用简单语法构建内联闭包的方式.
// sort方法 (The Sort Method)
// 将已知类型数组中的值进行排序.
// 一旦排序成功,sort(_:)方法会返回一个数组大小相同,包含同类型元素且元素已正确排序的新数组.原数组不会被修改
let names = ["Chris","Alex","Ewa","Barry","Daniella"]
// sort(_:)方法接受一个闭包,该闭包必须传入与数组元素类型一样的两个参数,并返回一个布尔值来确定到底是正向排序还是反向排序
let names2 = names.sort()
// 相当于
func backWards(s1:String,s2:String)->Bool{
return s1 > s2
}
var reversed = names.sort(backWards)// 倒叙
// 本质上只是写了一个单表达式(a>b)
func upWards(s1:String,s2:String)->Bool{
return s1 < s2
}
reversed = names.sort(upWards)// 正序
// 闭包语法表达式
// 格式如下
//{(parameters) -> returnType in
// statements
//}
// 闭包表达式语法可以使用常量,常量和inout类型作为参数,不能提供默认值.
// 也可以在参数列表的最后使用可变参数.元组也可以作为参数和返回值
reversed = names.sort({(s1:String,s2:String) -> Bool in
return s1 > s2
})
// 需要注意的是内联闭包参数和返回值类型声明与backwards(_:_:)函数类型声明相同.
// 闭包的函数体部分由关键字 in 引入.该关键字表示闭包的参数和返回值定义已经完成
reversed = names.sort({(s1:String,s2:String) -> Bool in return s1 > s2})
// 根据上下文推断类型 Inferring Type From Context
// 因为排序闭包函数是作为sort(_:)方法的参数传入的,swift可以推断其参数返回值类型.
// 上面的sort(_:)方法被一个字符串数组调用,因此其参数必须是(String,String)->Bool类型的函数.这意味着(String,String)和bool并不能作为函数表达式的一部分,返回值剪头也可以胜率,如下改写
reversed = names.sort({s1,s2 in return s1 > s2})
// 基本上所有的内联闭包表达式构造的闭包都可以被推断出类型,所以参数类型什么的都可以不写
// 单表达式闭包隐式返回 Implicit Return From Single-Expression Clossures
// 单行表达式闭包可以省略return关键字
reversed = names.sort({s1,s2 in s1 > s2})
// 这个例子中,sort(_:)方法的第二个参数类型明确了闭包必须返回一个Bool类型值,因为闭包只包含了一个单一表达式(s1 > s2),该表达式返回Bool类型.因此没有歧义,所以关键字return可以省略
// 参数名称缩写 Shorthand Argument Names
// Swift自动为内联闭包提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推
// 如果在闭包表达式中使用参数名的缩写,可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断.in关键字也可以被忽略
// 因为此时的闭包表达式完全由闭包函数体构成
reversed = names.sort({$0 > $1})
// $0 和 $1好像只能这么写
// 运算符函数 Opreator Functions
reversed = names.sort(>)
// 尾随闭包 (Trailing Closures)
// 如果你想要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性.尾随闭包是一个书写在函数括号之后的闭包表达式.函数支持其作为最后一个参数调用
func someFunctionThatTakesAClosure(closure:()->Void){
}
// 以下是不适用尾随闭包进行函数的调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
//在闭包表达式语法一节中作为sort(_:)方法参数的字符串排序闭包可以改写为:
reversed = names.sort() { $0 > $1 }
//如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉:
reversed = names.sort { $0 > $1 }
// 当闭包非常长的时候以至于不能一行进行书写时,会用到尾随闭包
// 当提供给数组的闭包应用于每个数组元素后,map(_:)方法将返回一个新的数组,数组中包含了与原数组中的元素意义对应的映射后的值
let digitNames = [
0:"Zero",1:"One",2:"Two",3:"Three",4:"Four",5:"Five",6:"Six",7:"Seven",8:"Eight",9:"Nine"
]
let numbers = [16,58,510]
// 可以通过传递一个尾随闭包map()讲Int数组转换为包含对应String类型的值的数组
let strings = numbers.map{
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output // 字典下标返回一个可选值,表明该键不存在时查找失败,这个地方确定有值,所以感叹号强制解包
number /= 10
}
return output
}
print(strings)
// 捕获值 Capturing Values
// 闭包可以在其被定义的上下文中捕获常量或变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内饮用和修改这些值
// swift中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其它函数内的函数.
// 嵌套函数可以捕获其外部函数的所有的参数以及定义的常量和变量
func makeIncrementor(forIncrement amount:Int)->()->Int{
var runningTotal = 0
func incrementor() -> Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
// makeIncrementor(forIncrement:)返回的类型是()->Int.这意味着返回的是一个函数
// 为了优化,如果一个值是不可变的,Swift可能会改为捕获并保存一份对值的拷贝
// Swift也会负责被捕获变量的所有内容管理工作,包括释放不再需要的变量.
// 使用上面的函数
let incrementByTen = makeIncrementor(forIncrement: 10)
// 定义了一个叫做incrementByTen的常量.该常量指向一个每次调用会将runningTotal变量增加10的incrementor函数,调用这个函数多次结果如下
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven() // 7
incrementBySeven() // 14
// 每个常量指向的函数参数不一样,所以函数也不一样,那么就是分别计数的
incrementByTen() // 40
// 注意:如果把闭包赋值给一个类实例的属性,并且该闭包通过访问类实例或其成员而捕获了该实例,将创建一个在闭包和该实例间的循环引用,swift使用捕获列表来打破这种循环引用
// 闭包是引用类型 Closures Are Reference Types
// 无论讲函数或闭包赋值给一个常量还是变量.实际上都是将常量或变量的值设置位对应函数或闭包的引用.
// 如果讲闭包赋值给不同的常量或者变量,那么指向的是一个闭包
let alseIncrementByTen = incrementByTen
alseIncrementByTen() // 50
// 有点蒙蔽,写个闭包玩儿
//var sum = {(a:Int,b:Int)->Int in
// return a + b
//}
//let a = 4
//let b = 2
//let sum1 = sum(a,b)
//var sum:((Int,Int) -> Int)! = nil
//sum = (+)
//let a = 1
//let b = 3
//let sum1 = sum(a,b)
// 下面这个不行
//var sum = ({+})
//let a = 1
//let b = 3
//let sum1 = sum(a,b)
// 非逃逸闭包 Nonescaping Closures
// 当一个闭包作为参数传到函数中.但是这个闭包在函数返回之后才能被执行,称这个闭包从函数中逃逸.
// 定义接受闭包作为参数的函数时,可以在参数名之前加标注@noescape用来指明这个闭包是不允许逃逸出这个函数的.
// 讲闭包标注@noescape能使编译器知道这个闭包的生命周期
// 闭包只能在函数体中被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文
func someFunctionWtihNoescapeClosure(@noescape closure:()->Void){
closure()
}
// 一种能使闭包"逃逸"出函数的方法是,将这个闭包存在一个函数外部定义的变量中.
var completionHandlers:[()->Void] = []
func someFunctionWithEscapingClosure(completionHandler:() -> Void){
completionHandlers.append(completionHandler)
}
// someFunctionWithEscapingClosure(_:)函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中.如果你试图将这个参数标记为@noescape,会得到编译错误
class SomeClass{
var x = 10
func doSomething(){
someFunctionWithEscapingClosure{ self.x = 100 }
someFunctionWtihNoescapeClosure{ 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"]
print(customersInLine.count)
let customerProvider = {customersInLine.removeAtIndex(0)}
print(customersInLine.count)
print(customerProvider())
print("Now serving \(customerProvider())")
print(customersInLine.count)
// 在闭包没被调用之前,这个元素是不会被移除的,只有被调用才会移除,如果一直不调用,就永远不会移除
// 注意:customerProvider的类型不是string,而是()->string.一个没有参数且返回值为String的函数
// 闭包作为参数传递给函数时,你能获得同样的延时求值行为
func serveCustomer(customerProvider:()->String){
print("Now serving \(customerProvider())!")
}
// serveCustomer({customersInLine.removeAtIndex(0)})
print(serveCustomer({customersInLine.removeAtIndex(0)}))
// serveCustomer(_:)接受一个返回顾客名字的显示的闭包.
func serveCustomer(@autoclosure customerProvider:()->String){
print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))
// 过度使用autoclosures会让你的代码变得难以理解,上下文和函数名应该能清晰的表明求值是被延迟执行的
// @autoclosure特性暗含了@noescape特性.
// 如果想让闭包可以"逃逸",应该用@autoclosure(escaping)
var customerProviders:[()->String] = []
func collectCustomerProviders(@autoclosure(escaping)customerProvider:() -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.removeAtIndex(0))
collectCustomerProviders(customersInLine.removeAtIndex(0))
print("Collected \(customerProviders.count) closures.")
//for customerProvider in customerProviders{
// print("Now serving \(customerProvider())!")
//}
// 在上面的代码中,collectCustomerProviders(_:)函数并没有调用传入的customerProvider闭包,而且讲闭包追加到了customerProviders数组中.
// 这个数组定义在函数作用域范围外,这意味着数组内的闭包将会在函数返回之后被调用.
// 因此,customerProvider参数必须允许"逃逸"出函数作用域