//闭包 这章有的地方不懂orz
//“闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似”
//“闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作”
//“在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
//全局函数是一个有名字但不会捕获任何值的闭包
//嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
//闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
//“Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
//利用上下文推断参数和返回值类型
//隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
//参数名称缩写
//尾随闭包语法”
//1.闭包表达式
//1.1 闭包表达式示例
//“Swift 标准库提供了名为 sorted(by:) 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,sorted(by:) 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 sorted(by:) 方法修改。”
let names = ["chris","alex","ewa","barry","daniella"]
func backward(_ s1: String,_ s2: String)->Bool{
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
print(reversedNames)
//1.2.闭包表达式语法
//一般形式
/*
{(parameters)-> returnType in
statements
}
*/
//“闭包表达式参数 可以是 in-out 参数,但不能设定默认值。也可以使用具名的可变参数(注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时候编译器将报错,)。元组也可以作为参数和返回值。
//所以上边的例子可以写作内联闭包:
reversedNames = names.sorted(by: {(s1:String,s2:String)->Bool in
return s1 > s2
})
print(reversedNames)
//“该例中 sorted(by:) 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
//“ 内联闭包 参数和返回值类型声明与 上个例子中backward(::) 函数类型声明相同。在这两种方式中,都写成了 (s1: String, s2: String) -> Bool。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。”
//“闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始”
//1.3根据上下文推断类型
//“因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。这意味着 (String, String) 和 Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略”
reversedNames = names.sorted(by: {s1,s2 in return s1 > s2})
print(reversedNames)
//“实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。”
//“尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包”
//1.4 单表达式闭包隐式返回
//“单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果”
reversedNames = names.sorted(by: {s1,s2 in s1 > s2})
print(reversedNames)
//“在这个例子中,sorted(by:) 方法的参数类型 明确了 闭包必须返回一个 Bool 类型值。因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回 Bool 类型值,因此这里 没有歧义 ,return 关键字可以省略”
//1.5 参数名称缩写
//“Swift 自动为 内联闭包 提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。”
//“如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略”
reversedNames = names.sorted(by: {$0 > $1})
print(reversedNames)
//“这个例子中,$0和$1表示闭包中第一个和第二个 String 类型的参数。”
//2. 尾随闭包
// “如果你需要将一个很长的闭包表达式作为 最后一个参数 传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签”
func someFunctionThatTakesAclosure(closure:()->Void){
//函数体部分
}
//不使用尾随闭包进行函数调用
someFunctionThatTakesAclosure(closure:{
//闭包主体部分
})
//使用尾随闭包进行函数调用
someFunctionThatTakesAclosure() {
//闭包主体部分
}
//上一节例子可以写为
reversedNames = names.sorted(){$0 > $1}
//如果比表表达式是函数或者方法的唯一参数,则可以把()省略掉
reversedNames = names.sorted{$0 > $1}
//“当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用”
//“Swift 的 Array 类型有一个 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 = [15,58,510]
let strings = numbers.map{
(number)->String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
print(strings)
//“map(:) 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。”
//“局部变量 number 的值由闭包中的 number 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 String,以表明存储映射值的新数组类型为 String。”
//“注意:字典 digitNames 下标后跟着一个叹号(!),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定 number % 10 总是 digitNames 字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的String类型的值。”
//“在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 map(:) 方法的括号内。”
//3. 值捕获
//“闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。”
//“Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。”
func makeIncrementer(forIncrement amount:Int)-> ()->Int{
var runningTotal = 0
func incrementer()->Int{
runningTotal += amount
return runningTotal
}
return incrementer
}
//“incrementer() 函数并没有任何参数,但是在函数体内访问了 runningTotal 和 amount 变量。这是因为它从外围函数捕获了 runningTotal 和 amount 变量的引用。捕获引用保证了 runningTotal 和 amount 变量在调用完 makeIncrementer 后不会消失,并且保证了在下一次执行 incrementer 函数时,runningTotal 依旧存在。
//注意:“为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。 Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量”
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() //返回值为10
incrementByTen() //返回值为20
incrementByTen() //返回值为30
//“如果你创建了另一个 incrementor,它会有属于自己的引用,指向一个全新、独立的 runningTotal 变量:”
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() //返回值为7
//“再次调用原来的 incrementByTen 会继续增加它自己的 runningTotal 变量,该变量和 incrementBySeven 中捕获的变量没有任何联系:”
incrementByTen() //返回值为40
//“如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用”
//4.闭包的引用类型
//“上面的例子中,incrementBySeven 和 incrementByTen 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。”
//“无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用 incrementByTen 是一个常量,而并非闭包内容本身。”
//“这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包”
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 返回50
//5.逃逸闭包
// “当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。”
//“一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用”
var completionHandles: [()->Void] = []
func someFunctionWithEscapingClosure(complationHandeler:@escaping ()->Void){
completionHandles.append(complationHandeler)
}
//“someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 @escaping,就会得到一个编译错误。”
//“将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self。比如说,在下面的代码中,传递到 someFunctionWithEscapingClosure(:) 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 self。相对的,传递到 someFunctionWithNonescapingClosure(:) 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 self”
func someFunctionWithNonescapeingClosure(closure:()->Void){
closure() //闭包类型的定义
}
class SomeClass{
var x = 10
func doSomething(){
someFunctionWithNonescapeingClosure {
x = 200 //闭包内容的定义
}
someFunctionWithEscapingClosure {
self.x = 100 //闭包内容的定义
}
}
}
let instance = SomeClass()
instance.doSomething() //闭包的调用
print(instance.x)
completionHandles.first?() //闭包的调用
print(instance.x)
//6.自动闭包
//“自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
//“下面的代码展示了闭包如何延时求值。”
var customersInLine = ["Chris","Alex","Andi","Barry","Edward"]
print(customersInLine.count)
//打印5
let customerProvider = {customersInLine.remove(at: 0)}
print(customersInLine.count)
//打印5
print("Now seving \(customerProvider())")
//打印 Now seving Chris
print(customersInLine.count)
//打印4
//“尽管在闭包的代码中,customersInLine 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,customerProvider 的类型不是 String,而是 () -> String,一个没有参数且返回值为 String 的函数”
//“闭包作为参数传递给函数时,你能获得同样的延时求值行为。”
// customersInLine is ["Alex","Andi","Barry","Edward"]
func serve(customer customerProvider:()->String){
print("Now serving \(customerProvider())")
}
serve(customer: {customersInLine.remove(at: 0)})
//打印出 Now serving Alex
//customerInLine is ["Andi","Barry","Edward"]
func severAgain(customer customerProvider: @autoclosure ()->String){
print("Now seving \(customerProvider())")
}
severAgain(customer: customersInLine.remove(at: 0))
//打印 Now serving Andi
//“ 过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
//“如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure 和 @escaping 属性”
//customerInline is ["Barry","Edward"]
var againCustomerProviders: [()->String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping ()->String){
againCustomerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("collected \(againCustomerProviders.count) closures")
// 打印 collected 2 closures
print(customersInLine)
//打印 [“Barry”,“Edward”]
for mycustomerPrivider in againCustomerProviders {
print("Now serving \(mycustomerPrivider())")
}
//打印 Now swrving Barry
//打印 Now seving Edward