Swift7-闭包
闭包表达式、尾随闭包、值捕获、闭包是引用类型、逃逸闭包、自动闭包。
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包和OC中的代码块以及其它一些编程语言中的匿名函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。Swift会管理在捕获过程中涉及到的所有内存操作。
函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
1)全局函数是一个有名字但不会捕获任何值的闭包。
2)嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包。
3)闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包。
Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
1)利用上下文推断参数和返回值类型。
2)隐式返回单表达式闭包,即单表达式闭包可以省略return关键字。
3)参数名称缩写。
4)尾随闭包语法。
闭包表达式:
嵌套函数是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。
闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
sorted方法:
Swift标准库提供了名为sorted(by:)的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。排序完成后,sorted(by:)方法会返回一个与原数组大小相同,包含同类型元素且已正确排序的数组。原数组不会被sorrted(by:)方法修改。
sorted(by:)方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数在第二个参数前面还是后面。
闭包表达式语法:
闭包表达式语法形式如下:
{ (parameters)->returnType in
statements
}
闭包表达式参数可以是in-out参数,但不能设定默认值。也可以使用具名的可变参数(但如果可变参数不放在参数列表的最后一位的话,调用闭包的时候编译器将报错。)元组也可作为参数和返回值。
var a=[1,2,5,3,9,6]
print(a.sorted(by:{
(_ a:Int,_ b:Int)->Bool in
return a<b
}))
在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
根据上下文推断类型:
因为排序闭包函数是作为sorted(by:)方法的参数传入的,Swift可以推断其参数和返回值的类型。如果sorted(by:)方法被一个整数数组调用,因此其参数必须是(Int,Int)->Bool类型的函数。所以该类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头->和围绕在参数周围的括号也可以被省略。
print(a.sorted(by:{
a,b in
return a<b
}))
表达式闭包隐式返回:
单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果。
print(a.sorted(by:{ x,y in x<y}))
参数名称缩写:
Swift自动为内联闭包提供了参数名称缩写功能,可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。
print(a.sorted(by:{$0 < $1}))
运算符方法:
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift的String类型定义了关于大于(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。这正与sorted(by:)方法的参数需要的函数类型相符合。
整数也有。(相当于C++的操作符重载)
print(a.sorted(by:<))
尾随闭包:
如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,不用写出它的参数标签。
如一个函数有一个函数类型参数:
func aaa(_ fc:()->Void){.....}
给函数传递一个闭包作为实参,可以在括号内写闭包,也可在括号外写闭包,两者都将闭包作为函数类型的参数:
aaa({(_ a:Int, _ b:Int)->Void in return a<b})
aaa(){(_ a:Int, _ b:Int)->Void in return a<b}
如sorted(by:)方法的最后一个参数(它只有一个参数)是一个(type,type)->Bool函数类型(type是从调用数组推断出的类型)。
以尾随闭包方式传递参数给sorted(by:)
print(a.sorted(){$0<$1})
//注意尾随闭包使用时不需要写出它的参数标签
Swift的Array类型有一个map(_:)方法,该方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素映射的值。
map(_:)也是将原数组元素映射到一个新的临时数组中,不会修改原数组的值。
值捕获:
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift中,可以捕获值的闭包的最简单形式是嵌套函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
嵌套函数可对外部函数的变量进行修改。
闭包是引用类型:
常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。
var b=0; let a={b+=1}
//相当于C++的const指针,不可以修改其指向的对象,但能修改其所指对象的属性
逃逸闭包:
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数逃逸。当定义接受闭包作为参数的函数时,可以在参数名之前标注 @escaping ,用来指明这个闭包是允许“逃逸”出这个函数的。
如:
var funcNames:[()->Void]=[]
func someFunc(_ comp:@escaping ()->Void){
funcNames.append(comp)
}
//闭包在函数添加进同类型的数组,并在函数体外仍可执行闭包功能。
将一个闭包标记为@escaping意味着必须在闭包中显式地引用self。用self指明调用闭包的实际对象。
自动闭包:
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
assert(condition:,message:,file:,line:)函数接受自动闭包作为它的condition参数和message参数:它的condition参数仅会在debug模式下被求值,它的message参数仅当condition参数为false时被计算求值。
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用和高计算成本的代码来说是很有益的,因为它使得你能控制代码的执行时机。
下面的代码展示了闭包如何延时求值:
var customersInLine=["cjj","xyy",htl"]
print(customersInLine.count) //5
let customerProvider={customersInLine.remove(at:0)}
print(customersInLine.count) //5
print("Now serving \(customerProvider())!") //Now serving cjj!
print(customersInLine.count) //4
自动闭包类似函数,但相比函数它能捕获其所在作用域的所有值而不仅仅是闭包内或参数值。
自动闭包赋予某个变量或常量后,通过该变/常量加括号调用执行闭包内的代码。
@autoclosure标记参数会将被标记的参数自动转化为一个闭包。
func aaa(_ comp:()->Int){...} //接受一个()->Int类型参数的函数
func bbb(_ comp: @autoclosure ()->Int){...} //此时可以将Int型传入值自动转换为一个闭包型参数。
@autoclosure类型于C++中的类类型隐式转换,不过@autoclosure自动将一个值(或某表达式)转换为返回该值类型(或该表达式结果类型)的闭包。