Swift教程之闭包详解剖析Word文档格式.docx
《Swift教程之闭包详解剖析Word文档格式.docx》由会员分享,可在线阅读,更多相关《Swift教程之闭包详解剖析Word文档格式.docx(12页珍藏版)》请在冰点文库上搜索。
![Swift教程之闭包详解剖析Word文档格式.docx](https://file1.bingdoc.com/fileroot1/2023-5/10/deef1378-66f7-4a3c-9a65-37b419a25f42/deef1378-66f7-4a3c-9a65-37b419a25f421.gif)
以下以同一个sort函数进行几次改进,每次函数都更加简洁,以此说明闭包表达式的优化。
Sort函数
Swift的标准函数库提供了一个名为sort的函数,它通过基于输出类型排序的闭包函数,给已知类型的数组数据的值排序。
一旦完成排序工作,会返回一个同先前数组相同大小,相同数据类型,并且的新数组,并且这个数组的元素都在正确排好序的位置上。
TheclosureexpressionexamplesbelowusethesortfunctiontosortanarrayofStringvaluesinreversealphabeticalorder.Here'
stheinitialarraytobesorted:
以下的闭包表达式通过sort函数将String值按字母顺序进行排序作说明,这是待排序的初始化数组。
复制代码代码如下:
letnames=["
Chris"
"
Alex"
Ewa"
Barry"
Daniella"
]
sort函数需要两个参数:
一个已知值类型的数组
一个接收两个参数的闭包函数,这两个参数的数据类型都同于数组元素。
并且
返回一个Bool表明是否第一个参数应排在第二个参数前或后。
这个例子是一组排序的字符串值,因此需要排序的封闭类型的函数(字符串,字符串)->
Bool。
构造排序闭包的一种方式是书写一个符合其类型要求的普通函数:
backwards,并将其返回值作为sort函数的第二个参数传入:
funcbackwards(s1:
String,s2:
String)->
Bool{
returns1>
s2
}
varreversed=sort(names,backwards)
//reversedisequalto["
如果backwards函数参数s1大于s2,则返回true值,表示在新的数组排序中s1应该出现在s2前。
字符中的“大于”表示“按照字母顺序后出现”。
这意味着字母“B”大于字母“A”,字符串“Tom”大于字符串“Tim”。
其将进行字母逆序排序,”Barry”将会排在“Alex”之后,以此类推。
但这是一个相当冗长的方式,本质上只是做了一个简单的单表达式函数:
(a>
b)。
下面的例子中,我们利用闭合表达式可以相比上面的例子更效率的构造一个内联排序闭包。
闭包表达式语法
闭合表达式语法具有以下一般构造形式:
{(parameters)->
returntypein
statements
闭包表达式语法可以使用常量参数、变量参数和inout类型作为参数,但皆不可提供默认值。
如果你需要使用一个可变的参数,可将可变参数放在最后,元组类型也可以作为参数和返回值使用。
下面的例子展示了上面的backwards函数对应的闭包表达式构造函数代码
reversed=sort(names,{(s1:
Boolin
})
需要注意的是声明内联闭包的参数和返回值类型与backwards函数类型声明相同。
在这两种方式中,都写成了(s1:
Bool类型。
然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
闭包的函数体部分由关键字in引入。
该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
因为这个闭包的函数体非常简约短所以完全可以将上面的backwards函数缩写成一行连贯的代码
Boolinreturns1>
s2})
可以看出sort函数的整体调用保持不变,还是一对圆括号包含两个参数变成了内联闭包形式、只不过第二个参数的值变成了。
而其中一个参数现在变成了内联闭包(相比于backwards版本的代码)。
根据上下文推断类型
因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。
sort期望第二个参数是类型为(String,String)->
Bool的函数,因此实际上String,String和Bool类型并不需要作为闭包表达式定义中的一部分。
因为所有的类型都可以被正确推断,返回箭头(->
)和围绕在参数周围的括号也可以被省略:
reversed=sort(names,{s1,s2inreturns1>
实际情况下,通过构造内联闭包表达式的闭包作为函数的参数传递给函数时,都可以判断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
同样,如果你希望避免阅读函数时可能存在的歧义,你可以直接明确参数的类型。
这个排序函数例子,闭包的目的是很明确的,即排序被替换,而且对读者来说可以安全的假设闭包可能会使用字符串值,因为它正协助一个字符串数组进行排序。
单行表达式闭包可以省略return
单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversed=sort(names,{s1,s2ins1>
在这个例子中,sort函数的第二个参数函数类型明确了闭包必须返回一个Bool类型值。
因为闭包函数体只包含了一个单一表达式(s1>
s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。
参数名简写
Swift自动为内联函数提供了参数名称简写功能,可以直接通过$0,$1,$2等名字来引用闭包的参数值。
如果在闭包表达式中使用参数名称简写,可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。
in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversed=sort(names,{$0>
$1})
在这个例子中,$0和$1表示闭包中第一个和第二个String类型的参数。
运算符函数
运算符函数实际上是一个更短的方式构造以上的表达式。
reversed=sort(names,>
)
更多关于运算符表达式的内容请查看OperatorFunctions。
2、Trailing闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用trailing闭包来增强函数的可读性。
Trailing闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。
funcsomeFunctionThatTakesAClosure(closure:
()->
()){
//functionbodygoeshere
//here'
showyoucallthisfunctionwithoutusingatrailingclosure:
someFunctionThatTakesAClosure({
//closure'
sbodygoeshere
showyoucallthisfunctionwithatrailingclosureinstead:
someFThatTakesAClosure(){
//trailingclosure'
注意
如果函数只需要闭包表达式一个参数,当您使用trailing闭包时,您甚至可以把()省略掉。
在上例中作为sort函数参数的字符串排序闭包可以改写为:
reversed=sort(names){$0>
$1}
当闭包非常长以至于不能在一行中进行书写时,Trailing闭包就变得非常有用。
举例来说,Swift的Array类型有一个map方法,其获取一个闭包表达式作为其唯一参数。
数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。
具体的映射方式和返回值类型由闭包来指定。
当提供给数组闭包函数后,map方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
下例介绍了如何在map方法中使用trailing闭包将Int类型数组[16,58,510]转换为包含对应String类型的数组["
OneSix"
FiveEight"
FiveOneZero"
]:
letdigitNames=[
0:
"
Zero"
1:
One"
2:
Two"
3:
Three"
4:
Four"
5:
Five"
6:
Six"
7:
Seven"
8:
Eight"
9:
Nine"
]
letnumbers=[16,58,510]
上面的代码创建了整数数字到他们的英文名字之间映射字典。
同时定义了一个准备转换为字符串的整型数组。
你现在可以通过传递一个trailing闭包给numbers的map方法来创建对应的字符串版本数组。
需要注意的时调用numbers.map不需要在map后面包含任何括号,因为只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过trailing方式进行撰写:
letstrings=numbers.map{
(varnumber)->
Stringin
varoutput="
"
whilenumber>
0{
output=digitNames[number%10]!
+output
number/=10
returnoutput
//stringsisinferredtobeoftypeString[]
//itsvalueis["
map在数组中为每一个元素调用了闭包表达式。
您不需要指定闭包的输入参数number的类型,因为可以通过要映射的数组类型进行推断。
闭包number参数被声明为一个变量参数(变量的具体描述请参看ConstantandVariableParameters),因此可以在闭包函数体内对其进行修改。
闭包表达式制定了返回值类型为String,以表明存储映射值的新数组类型为String。
闭包表达式在每次被调用的时候创建了一个字符串并返回。
其使用求余运算符(number%10)计算最后一位数字并利用digitNames字典获取所映射的字符串。
注意:
字典digitNames下标后跟着一个叹号(!
),因为字典下标返回一个可选值(optionalvalue),表明即使该key不存在也不会查找失败。
在上例中,它保证了number%10可以总是作为一个digitNames字典的有效下标key。
因此叹号可以用于强展开(force-unwrap)存储在可选下标项中的String类型值。
从digitNames字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。
(在表达式number%10中,如果number为16,则返回6,58返回8,510返回0)。
number变量之后除以10。
因为其是整数,在计算过程中未除尽部分被忽略。
因此16变成了1,58变成了5,510变成了51。
整个过程重复进行,直到number/=10为0,这时闭包会将字符串输出,而map函数则会将字符串添加到所映射的数组中。
上例中trailing闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在map函数的括号内。
3、获取值
闭包可以在其定义的范围内捕捉(引用/得到)常量和变量,闭包可以引用和修改这些值,即使定义的常量和变量已经不复存在了依然可以修改和引用。
牛逼吧、
在Swift中最简单形式是一个嵌套函数,写在另一个函数的方法里面。
嵌套函数可以捕获任何外部函数的参数,也可以捕获任何常量和变量在外部函数的定义。
看下面这个例子,一个函数方法为makeIncrementor、这是一个嵌套函数,在这个函数体内嵌套了另一个函数方法:
incrementor,在这个incrementor函数体内有两个参数:
runningTotal和amount,实际运作时传进所需的两个参数后,incrementor函数每次被调用时都会返回一个runningTotal值提供给外部的makeIncrementor使用:
funcmakeIncrementor(forIncrementamount:
Int)->
Int{
varrunningTotal=0
funcincrementor()->
runningTotal+=amount
returnrunningTotal
returnincrementor
而函数makeincrementor的返回类型值我们可以通过函数名后面的()->
int得知返回的是一个Int类型的值。
如需想学习了解更多地函数返回类型,可以参考:
FunctionTypesasReturnTypes.(超链接跳转)
我们可以看见crementor这个函数体内首先定义了一个整型变量:
runningtotal,初始值为0,而incrementor()函数最终运行的出来的返回值会赋值给这个整型变量。
makeincrementor函数()中向外部抛出了一个forIncrement参数供外部穿参进来、一旦有值进入函数体内会被函数实例化替代为amount,而amount会被传递进内嵌的incrementor函数体中与整型常量runningTotal相加得到一个新的runningTotal并返回。
而我们这个主函数要返回的值是Int类型,runningTotal直接作为最终值被返回出去、makeincrementor函数()执行完毕。
makeincrementor函数()在其内部又定义了一个新的函数体incrementor,作用就是将外部传递过来的值amount传进incrementor函数中与整形常量runningTotal相加得到一个新的runningTotal,
单独的看incrementor函数、你会发现这个函数不寻常:
因为incrementor函数没有任何的参数,但是在它的函数方法体内却指向runningTotal和amount,显而易见、这是incrementor函数获取了外部函数的值amount,incrementor不能去修改它但是却可以和体内的runningTotal相加得出新的runningTotal值返回出去。
不过,由于runningtotal每次被调用时都会相加改变一次实际值,相应地incrementor函数被调用时会去加载最新的runningtotal值,而不再是第一次初始化的0.并且需要保证每次runningTotal的值在makeIncrementor函数体内不会丢失直到函数完全加载完毕。
要能确保在函数体内下一次引用时上一次的值依然还在。
Swift中需要明确知道什么时候该引用什么时候该赋值,在incrementor函数中你不需要注解amount和runningTotal。
Swift还负责处理当函数不在需要runningTotal的时候,内存应该如何去管理。
这里有一个例子makeIncrementor函数:
letincrementByTen=makeIncrementor(forIncrement:
10)
4、引用类型闭包
在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量在闭包的状态下依然可以被修改。
为何?
很简单,因为函数和闭包是引用类型。
当你指定一个函数或一个闭包常量/变量时、实际上是在设置该常量或变量是否为一个引用函数。
在上面的例子中,它是闭合的选择,incrementByTen指的是恒定的,而不是封闭件本身的内容。
这也意味着,如果你分配一个封闭两种不同的常量或变量,这两个常量或变量将引用同一个闭包:
letalsoIncrementByTen=incrementByTen
alsoIncrementByTen()
//returnsavalueof50