Swift独立函数代码块(闭包)
闭包是功能性自包含模块,可以在代码中被传递和使用。Swift中的闭包与C/OC中的blocks以及其他一些编程语言中的lambdas相似。
闭包可以捕获和存储它所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift会为你管理在捕获过程中涉及到的内存操作。
在函数里的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
1. 全局函数是一个有名字但不会捕获任何值的闭包。
2. 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包。
3. 闭包表达式是一个利用轻量级语法缩写的可以捕获其上下文中变量或常量值的没有名字的闭包。
Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化有:
1. 利用上下文推断参数和返回类型。
2. 单表达式闭包可以省略return关键字。
3. 参数名称缩写。
4. Trailing闭包语法。
闭包表达式
闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
sort函数
Swift标准库提供了sort函数,会根据你提供的排序闭包将已知类型数组中的值进行排序。一旦排序完成,函数会返回一个与元素组大小相同的新数组,该数组中包含已经正确排序的同类型元素。
① 使用sort函数对一个String类型的数组进行字母逆序排序:
let names = ["Jolin","Elva","Fish","Stefanie"]
func backwards(s1: String,s2: String)->Bool{
return s1 > s2
}
var reversed = sort(names,backwards)
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
该例子对一个String类型的数组进行排序,因此排序闭包需为(String,String)->Bool类型的函数。
提供排序闭包的一种方法是撰写一个符合其类型要求的普通函数,并将其作为sort函数的第二个参数传入。
利用闭合表达式语句构造一个内联排序闭包。
闭包表达式语法:
闭包表达式语法有如下一般形式:
{(parameters)-> returnType in statements}
闭包表达式语法可以使用常量,变量和inout类型作为参数,不提供默认值。
也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{(s1: String,s2: String) -> Bool in return s1 > s2})
println(reversed)
[Stefanie, Jolin, Fish, Elva]
需要注意的是内联闭包参数和返回值类型声明与backwards函数类型声明相同。在这两种方式中,都写成了(s1: String,s2: String
)-> Bool。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
根据上下文推断类型
因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。sort期望第二个参数是类型为(String,String)->Bool的函数,因此实际上String,String和Bool类型并不需要作为闭包表达式定义中的一部分。以为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{s1,s2 in return s1 > s2})
println(reversed)
[Stefanie, Jolin, Fish, Elva]
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型。这意味着你几乎不需要利用完整格式构造任何内联闭包。
单行表达式闭包可以省略 return
单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{s1,s2 in s1 < s2})
println(reversed)
//[Elva, Fish, Jolin, Stefanie]
在这个例子里,sort函数的第二个参数函数类型明确了闭包必须返回一个Bool类型值。因为闭包函数体只包含了一个单一表达式(s1 < s2),该表达式返回Bool类型值。无歧义,return关键字可以省略。
参数名称缩写
Swift自动为内联函数提供了参数名称缩写功能,你可以直接通过$0,$1,$2来顺序调用闭包的参数。
如果你在闭包表达式中使用参数名称缩写,你可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{$0 > $1})
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
运算符函数
Swift的String类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。而正好与sort函数的第二个参数需要的函数类型相符合。因此,你可以简单传递一个大于号,Swift可以自动推断你想使用大于号的字符串函数实现:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,>)
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
Trailing闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用trailing闭包来增强函数的可读性。Trailing闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其最后一个参数调用。
func someFunctionThatTakesAClosure(closure:()->()){
//函数体部分
}
//以下是不使用trailing闭包进行函数调用
someFunctionThatTakesAClosure({
//闭包主题部分
})
//以下是使用trailing闭包进行函数调用
someFunctionThatTakesAClosure(){
//闭包主题部分
}
注意:如果函数只需要闭包表达式一个参数,当你使用trailing闭包时,你甚至可以把()省略掉。
上述中作为sort函数参数的字符串排序闭包可以改写为:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names){
$0 > $1
}
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
当闭包非常长以至于不能在一行中进行书写时,Trailing闭包变得非常有用。
举例:Swift的Array类型有一个map方法,其获取一个闭包表达式作为其唯一参数。数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。具体的映射方式和返回值类型由闭包来指定。
当提供给数组闭包函数后,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]
let strings = numbers.map{
(var number) -> String in var output = ""
while number > 0{
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
println(strings)
//[OneSix, FiveEight, FiveOneZero]
//strings 常量被推断为字符串类型数组,即String[]
捕获(Caputure)
闭包可以在其定义的上下文中捕获常量或变量。即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数内饮用和修改这些值。
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
func makeIncrementor(forIncrement amount: Int) ->() ->Int{
var runningTotal = 0
func incrementor() ->Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement:10)
println(incrementByTen())
println(incrementByTen())
println(incrementByTen())
//10
//20
//30
注意:如果你闭包分配给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例。你将创建一个在闭包和实例间的强引用环。Swift使用捕获列表来打破这种强引用环。
闭包是引用类型
incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值,这是因为函数和闭包都是引用类型。
无论你将函数/闭包赋值给一个常量还是变量,你实际上都是将常量/变量的值设置为对应函数/闭包的引用。
incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。
这也就意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包。
func makeIncrementor(forIncrement amount: Int) ->() ->Int{
var runningTotal = 0
func incrementor() ->Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement:10)
println(incrementByTen())
println(incrementByTen())
println(incrementByTen())
let incrementByTenAlso = incrementByTen
println(incrementByTen())
//10
//20
//30
//40