swift闭包_swift闭包表达式

分享到:

闭包

swift闭包是自包的函数组块,可以在您的代码中被传递和使用。Swift的闭包类似于C和Objective-C语言的blocks,和其他编程语言的lambdas

闭包可以捕获和存储它们被定义的范围内的任何常量和变量的引用。它的特点就是用于关闭这些常数和变量,因此命名为“closures”。Swift可帮你处理所有capturing的内存管理。

注:如果你不熟悉“capturing”的概念,不要担心。我们将会在下面捕获值的部分对它做详细的解释。

广泛及嵌套函数,实际上是闭包的特殊情况,如在函数部分的介绍,。Closures采用下列三种形式之一:

   全局函数是一种有一个函数名,但不会捕获任何值的Closures。

   嵌套函数是有一个名称、可以从他们的封闭函数捕获值的闭包。

   闭合表达式是用lightweight语法写的、可以从他们的周围上下文捕获值的未命名闭包。

Swift的闭合表达式有一个干净,清晰的风格,优化过程鼓励常见场景简单,整洁的语法。这些职能包括:

   通过上下文推断参数和返回值的类型。

   源自单表达式闭包的隐含返回值

   速记参数名

   尾随闭包句法

闭合表达式

嵌套函数,如在嵌套函数一章的介绍,是命名和定义的代码组块作为一个更大的函数的一部分的方便手段。但是,有时写短版本的没有完整名称的函数类似的结构是有益的。当你处理需要将其它函数作为它们的一个或多个引数的函数等功能为一体的论据或更多的功能,尤其如此。

闭合表达式是一种精简编写内联闭包的方式。闭合表达式提供了一些语法上的优化,可以在不损害清晰和意义的前提下以最简单的形式写闭包。下面的闭合表达式示例语法上的优化,通过优化一个sort函数的几次改进,每次的表达都以更简洁的方式表述相同的函数。

排序函数

Swift的标准库提供了一个叫做sort的函数,这个函数根据您提供的分拣闭包对已知类型的值的组进行分类。一旦它完成了分拣过程中,排序函数返回与原来的相同类型和大小的新数组,其中的组分都已经在正确的排序顺序上。

下面的闭合表达式示例使用排序功能,将字符串值以字母倒序排序。这里是要进行排序的最初的数组:

 
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
 

FAI排序函数有两个自变量:

一个已知类型值得数组。

一个闭包取同类型的两个参数作为数组的内容, 一旦该值被存储,其将返回and returns a Bool 值来判断第一个值应于第二个值前还是后出现。如果第一个值应出现在第二个值之前,排序闭包则需返回true, 否则返回false。

这个例子是对字符串值的数组进行排序,所以分拣闭包必须是类型的函数(String, String) -> Bool。

提供分拣闭包的一种方法是编写一个正确类型的正常函数,并把它作为排序函数的第二个参数传递:

 
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = sort(names, backwards)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
 

如果第一个字符串(S1)大于第二个字符串(S2),向后函数将返回true,表示在排序后的数组中S1应该出现S2之前,。在字符串中的字符,“较大”意思是 “在字母表中出现的较晚”。这意味着,字母“b”“比”字母“a”大,字符串"Tom"大于字符串"Tim"。如果将"Barry"放在"Alex"之前,那么这将提供一个字母反序排列,依此类推。

然而,这是以一个相当冗长的方式来写本质上的一个单一表达式函数(a > b)。在这个例子中,最好用闭合表达式语法写排序闭包。

闭合表达式句法

闭合表达式句法具有下列通式:

 
{ ( parameters ) -> return type in
statements
}
 

闭包表达式语法可以使用常量参数,变量参数和 INOUT 参数。不能提供默认值。只要你命名一个可变参数,并将其置于参数列表的最后,则可以使用可变参数。元组也可以被用作参数类型和返回类型。

下面的例子显示了早期 backwards 函数的闭包表达式版本:

 
reversed = sort(names, { (s1: String, s2: String) -> Bool in
return s1 > s2
})
 

请注,这个内嵌闭包参数和返回类型的声明与 backwards 函数的声明相同。 在这两种情况下,它被写作 (s1: String, s2: String) -> Bool。然而,对于内嵌闭包表达式,参数和返回类型都写在大括号内,不是在括号外面。

闭包体的开始通过关键字进行介绍。这个关键字表明闭包的参数和返回类型的定义已经完成,闭包体即将开始

因为闭包体很短,它甚至可以写入单行中:

 
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )
 

这说明对排序函数的全部调用保持不变。一对圆括弧仍然可以包含函数的整组参数。然而,现在,这些参数中的其中一个是一个内联闭包。

通过上下文推断类型

因为排序闭包作为函数参数被传递,Swift 可以从排序函数第二个参数类型推断出它的参数和它返回值的类型。这个参数期望的函数类型为 (String, String)-> Boo。这意味着 和 类型不必写成闭包表达式定义的一部分。因为可推断所有类型,返回箭头和参数名称周围的括号也可以省略

 
reversed = sort(names, { s1, s2 in return s1 > s2 } )
 

把一个闭包作为内联闭包表达式传递给函数时,总可以推断参数类型和返回类型。因此,你很少需要在其最充分的形式下编写一个内联闭包。

然而,如果你想,你可以使类型明确。如果这样做可避免读者对你的代码产生歧义,则鼓励这样做。在排序函数中,闭包的目的与排序正在发生的事实无关。而且读者推断闭包很可能正在处理 值是安全的,因为它正在协助一个字符串数组的排序。

源自单表达式闭包的隐含返回值

单表达式闭包可通过省略其声明中的返回关键字隐式地返回单表达式的结果,和在之前示例版本中相同:

 
reversed = sort(names, { s1, s2 in s1 > s2 } )
 

在这里,排序函数的第二个参数的函数类型清楚地表明,必须经闭包返回。因为闭包体包含返回 的单表达式 ,没有歧义,所以返回关键字可以省略。

速记参数名称

Swift 自动提供内联闭包的速记参数名称,可用来引用闭包参数值,使用 $ 0、$ 1、$ 2 等等名称。

如果你在闭包表达式中使用这些速记参数名称,你可以从闭包的定义中省略其参数列表。速记参数名称的数量和类型会从预期的函数类型推断。关键字也可以省略,因为闭包表达完全由其主体组成:

 
reversed = sort(names, { $0 > $1 } )
 

在这里,$0 和 $1 分别是指该闭包的第一个和第二个实参。

运算符函数

实际上,有一个更简短的方式,可以表达上述闭包表达式。Swift 的 类型将字符串特有的大于运算符为一个函数,它有两类 参数,并返回一个类型为 BOO 的值。这与排序函数的第二个参数所需的函数类型完全匹配。

因此,你可以只传递大于运算符,Swift 将推断出你想要使用其字符串的特有实现:

 
reversed = sort(names, >)
欲了解更多关于运算符函数的信息,请参阅运算符函数部分的内容。
 

尾随闭包

如果你需要将一个闭包表达式传递给一个函数作为其最后的实参,且这个闭包表达式很长,则反而将其写成尾随闭包很有效。尾随闭包是写于其支持的函数调用括号外(和后)的闭包表达式:

 
func someFunctionThatTakesAClosure(closure: () -> ()) {
// function body goes here
}
// here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
// closure's body goes here
})
here's how you call this function with a trailing closure instead: someFunctionThatTakesAClosure() { trailing closure's body goes here
 

注:如果一个闭包表达式作为函数的唯一实参被提供,且你提供那个表达式作为一个尾随闭包,则当你调用该函数时你不需要在函数的名称后写一对圆括号。

上述闭包表达式语法部分的字符串分拣闭包可以作为尾随闭包写在排序函数的括号之外:

 
reversed = sort(names) { $0 > $1 }
 

当闭包足够长,以至于不可能在同一行中写,尾随闭包最有用。作为一个例子,Swift 的数组类型有一个映射方法,即将闭包表达式作为其唯一实参。闭包为数组中的每个项目调用一次,并且返回该项目的替代映射值(可能是某些其它类型的)映射的性质和返回值的类型是留于闭包指定。 应用提供的闭包到每个数组元素后,映射方法以与它们在原始数组中的相对应值的相同顺序返回一个包含所有的新映射值的一个新数组。

以下是你如何用利用尾随闭包的映射方法将一个 Int 值数组转换为 值数组。数组 [16,58,510] 用于创建新的数组。 ["OneSix", "FveEght", "FiveOneZero"]:

 
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]
 

上面的代码创建了整数及其英文版名称间的映射字典。它还定义了一个准备被转换成字符串的整数数组。

你现在可以使用数字数组创建一个 值数组,通过将一个闭包表达式作为尾随闭包传递给数组的映射方法。注,调用 numbers.map 不需要在映射后使用任何括号,因为映射的方法只有一个参数,并且该参数作为一个尾随闭包提供:

 
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
strings is inferred to be of type String[]
value is ["OneSix", "FiveEight", "FiveOneZero"]
 

对于数组中的每一项,映射函数都会调用一次闭包表达式。无需指定闭包输入参数类型、数量,因为该类型可从要映射数组中的值进行推断。

在此例中,闭包数量参数被定义为一个可变参数,如常量和变量参数部分所描述,以便可在闭包体内修改该参数值,而不是声明一个新的本地变量和将传递的数字值分配给它。闭包表达式还指定 的返回类型,以表明将要存储在映射输出数组中的类型。

闭包表达式在每一次被调用时都会构建一个名为 output 的字符串。其通过使用剩余运算符 (number % 10) 计算数字的最后一位,并使用这个数字在 digitNames 词典中查找适当的字符串。

注:digitNames 字典下标调用后跟一个感叹号,因为字典下标返回一个可选值,表明如果该关键词不存在,词典查找可能失败。在上例中,保证  % 10 始终是有效的 digitNames 字典下标关键词,所以感叹号是用来强行解开存储在下标可选返回值中的值。

从 digitNames 词典中检索到的字符串被添加到输出前面,有效地建立了数字的相反字符串版本。(表达式 10 将 16 赋值为 6,将 58 赋值为 8,将 510 赋值为 0。)

然后数字变量除以 10,因为它是一个整数,在除法过程中被舍去余数,所以 16 变成 1,58 变为 5,510 变为 51。

重复该过程,直到 number10等于 0 时输出字符串由闭包返回,并且由映射函数添加到输出数组。

在上例中使用尾随闭包语法,巧妙地在闭包支持的函数后融入了闭包功能,而不需要将整个闭包包含到映射函数的外括号内。

获取值

闭包可以在其定义的范围内捕捉常量和变量。闭包可以引用和修改这些值,即使定义的常量和变量已经不复存在了依然可以修改和引用。

在 Swift 中,闭包的最简单形式是一个嵌套函数,写入另一个函数内。嵌套函数可以捕获任何外部函数的参数,也可以捕获外部函数中定义的任何常量和变量。

下例为称之 makeincrementor 的函数,其中包含一个名为 incrementor 的嵌套函数。嵌套的增量函数从它周围的环境中捕获两个值 runningTota 和 amount。捕获这些值后,由makeIncrementor 返回作为闭包的 incrementor,每次被调用时产生增量 runningTota。

 
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
 

makeIncrementor 的返回类型为 () -> Int。这意味着,它返回的是一个函数,而不是一个简单的值。它所返回的函数没有参数,并在每次调用时返回一个 Int 值。如需了解函数如何返回其他函数的相关信息,请参见作为返回类型的函数类型。

该 makeIncrementor 函数定义一个名为 runningTotal 的整型变量,用于存储当前正在运行且将要被返回的增量总量。此变量的初始值为 0。

该 makeIncrementor 函数含有一个外部名称为 forIncrement 且本地名称为 amount 的 参数。传递到此参数的实参值指定每次返回的函数 incrementor 被调用时的 runningTotal 的增量。

makeIncrementor 定义了一个名为 incrementor 的嵌套函数,它执行实际的递增。这个函数只是将 amount 添加到 runningTotal,并将结果返回。 单独看时,嵌套函数 incrementor 可能看起来不同寻常:

 
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
 

函数没有任何参数,但在其函数体内却指向 runningTota 和 amount。它通过从其周围的函数捕获 runningTotal 和 amount 的现有值并在其函数体内使用它们来执行此操作。

因为它没有修改 amount,incrementor 实际上捕获和存储存储在 amount 中的值副本。这个值与新建的 incrementor 函数一起存储。

然而,由于 runningTotal 每次被调用时都会修改其变量,所以 incrementor 捕获runningTota变量的当前引用,而不是仅仅是初始值的拷贝。捕获引用确保当调用 makeIncrementor 结束时 runningTota 不会消失,并确保 runningTota 在下一次调用 incrementor 函数时仍可用。

注:Swift 决定什么应该通过引用进行捕获,什么应该通过数值进行复制。您不需要注释 amount 或 runningTotal,说明它们可在嵌套函数 中使用。当 incrementor 函数不再需要时,Swift 也处理处置 runningTotal 时涉及的所有内存管理。

下例为正在运行的函数 makeIncrementor:

 
let incrementByTen = makeIncrementor(forIncrement: 10)
 

本例设置名为 incrementByTen 的一个常量指代 incrementor 函数,该函数在每次调用 runningTotal 变量时会增加 10 至其中。多次调用该函数表明该行为正在运行:

 
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
 

如果您创建另一个 incrementor,它会让其自身的引用指代一个新的、独立的 runningTota 变量。在下例中,incrementBySeven 捕获一个新 runningTota 变量的引用,而且该变量与 incrementByTen 捕获的变量无关:

 
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// returns a value of 7
incrementByTen()
// returns a value of 40
 

注:如果你将闭包指定给类实例属性,而且该闭包通过引用实例或其成员捕获该实例,你将在闭包和实例间创建强引用循环。Swift 采用捕获列表来打破这些强引用循环。欲了解更多相关信息,请参阅闭包强引用循环部分的内容。

 

引用类型闭包

在上例中,incrementBySeven 和 incrementByTen 是常量,但这些常量所指的闭包仍可增加它们已经捕获的 runningTotal 变量。这是因为函数和闭包都是引用类型。

当你指定一个函数或一个闭包常量或变量时,你实际上是在设置该常量或变量为函数或闭包引用。在上例中, 闭包的选择是 incrementByTen 指的是闭包常量,而不是闭包本身的内容。

这也意味着,如果你为两个不同的常量或变量分配一个闭包,这两个常量或变量将引用同一个闭包:

 
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC