swift 笔记 (七) —— 闭包

闭包
闭包是个自包含的,可以在代码中传递的“块”。。。好吧,不纠结定义,继续。
swift的闭包,有点像C和Objective-C语言里的 代码块 {……} 

闭包可以捕捉并且保存在它所被定义的那个东西的上下文中定义的常量和变量。

事实上,在上一个笔记——函数,中的 全局函数和嵌套函数,全都是闭包的特殊形式

全局函数——是一个带有函数名的,并不捕捉任何值的函数。
嵌套函数——是一个带有函数名的并且从它被定义的外层函数中捕捉数值的函数。
闭包表达式——是一个不带函数名的,使用一种轻便的语法书写方式,并且可以从它被定义的外层函数中捕捉数值的函数。

从上面三个类型的函数的描述,可以看出,其实所谓的闭包(闭包表达式),就是一个不带函数名的嵌套函数,只不过,他的书写方式有点特别罢了。

swift 的闭包,鼓励我们把他写得短小并且希望没有废话的方式。。。我们都懂的,这种所谓的精巧小巧短小尽量没有废话的方式,有时候会让人发狂,这正是闭包,这个玩意的难于理解之处吧!

不过,没关系,官方给的文档,足以让我们从一个让正人类能读懂的函数开始,慢慢变成一个非人正人类也可能会抓狂的函数的特殊形式——闭包。

刚才说到闭包推荐我们把函数尽量优化到所谓的短小和没有废话,那么先看一下,哪些部分会被“优化”掉:
1. 能够根据上下文推理出来的参数 以及 函数的返回值类型
2. 只有一条语句构成的简单表达式的”return"关键字
3. 变量的名字
4. Trailing closure syntax (后关闭的语法: 这个东西,我没有找到适合的解释,我自己的理解它类似于仅仅在函数的结尾才有返回的方式,也就是在任何一个函数体内,任何一个分支都不会打断函数的执行,一直到函数的最后一行的一个return或者无返回值之类的)

在swift的基本库中,有一个叫作sort的函数,它可以给Array中的元素排序,然后返回一个与传入的Array一样类型和元素个数的新的Array。

sort的原型:
sort(array: T[], pred: (T, T) -> Bool)   //这是个泛型, pred是它内部用的,不用管,总之,参数2是个函数
如果参数2是个函数,这个不理解的话,还是先回头去把函数那部分复习一遍吧

这样可能不太容易读,那么我把它变形一下:
sort(myArray: Int[],  (Int, Int) -> Bool)   //这是个Int型的处理方式
(感谢 swift技术交流第一平台(355277)的群友 packy(974871365) 指出之前写的时候,sort的右括号被我弄丢了,现在改好了 )

sort的第一个参数是个Int型数组, 第二个参数是个函数,这个函数有两个参数,返回值是Bool型
当然我们也可以把原型中的T换成其它的类型,比如:
sort(myArray: String[],  (String, String) -> Bool)
sort(myArray: Double[], (Double, Double) -> Bool)
以此类推,随便换成我们想要的形式 (泛型的意义如此)

let names = [“Chris”, “Alex”, “Ewa”, “Barry”, “Daniella”]

func myCompareFunc (s1: String, s2: String) -> Bool {    // 这个函数,跟sort的参数2 函数原型一样
     return s1 > s2
}

var reversedNames = sort(names, myCompareFunc)   //得到了一个字母序倒序的数组

到这里,应该都很容易理解了,sort的第一个参数,是个String数组, 第二个参数是个有两个参数并且返回Bool型的函数

sort会把names数组中的元素,按着它已经写好了的算法,取出某两个下标的值,然后传入到第二个参数定义的那个函数中,而第二个参数的函数是我们写的,如果参数1> 参数2,就返回true,这里是字符串,返回的字母表中字母的顺序,b排在a后面,所以b > a是true

前面说了,闭包有点像C语言和Objective-C里面的“代码块”, 所以,闭包的形式是这样的:
{                                                        //代码块开始
     (参数) -> 返回值类型  in               //注意这个语法:  in 之后就是函数体了
          函数体
}                                                        //代码块结束
更前面的时候,已经说了,闭包没有函数名。。。所以上面根本就没有函数名,但他仍然是个函数。

我们用闭包的形式,来改写上面写过的sort的调用,以及 myCompareFunc的定义:

reversedNames = sort(names,   //参数2在下面
{                                                        //代码块开始
     (s1: String, s2: String) -> Bool  in     //注意这个语法:  in 之后就是函数体了
          return s1 > s2                         
}                                                        //代码块结束
// sort调用结束

这里sort的第二个参数,已经被我们替换成了上面提到的闭包的形式了
现在,我把一些换行符和一些空格以及注释删掉,仅仅是删换行和空格和注释哦:

reversedNames = sort(names, {(s1 :String, s2 :String) ->Bool in return s1 > s2} )   //对比一下,我没有多删东西吧

因为,这个闭包中的所有的参数类型(与sort的第一个参数的基本类型相同) 以及返回值类型(sort要求第二个函数的返回值类型为Bool),全都是可以推断出来的,所以,类型可以省略,于是上面的这一条调用,就变成了:

reversedNames = sort(names, {s1, s2 in  return s1 > s2} )   //我只删了参数类型和返回值类型

在上面的红字部分,提到,闭包中,可以省略的东西, 2.只有一条语句构成的简单表达式的”return”关键字, 所以,我们继续把return这个关键字也删掉, sort的调用就变成了:

reversedNames = sort(names, {s1, s2 in s1 > s1})  (感谢 swift苹果开发超级群(191026105)的群友 阿康ゞ(409767959) 指出这里第二个变量笔误 )
reversedNames = sort(names, {s1, s2 in s1 > s2})
到这里,是不是sort已经变得不太容易读了。。。但还没完:

对于inline的闭包, swift还提供了 Shorthand Argument Names,作为参数的简写,以省去参数名,  $0代表第一个参数,$1代表第二个参数, $2……. 甚至连 in 都可以省了, 于是 sort调用的新版本:

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

操作符函数
上面的sort调用已经够短了,但是对于操作符 > 来说,它需要的是两个参数,并且返回值为Bool型,这正好符合了sort函数第2个参数所要求的函数形式,于是,我们可以把 > 当作函数,直接放在sort的第2个参数位置:

reversedNames = sort(names, >)           //人类已经无法阻止闭包的简化了......
关于操作符作为函数,如果并不了解操作符重载的话,确实不好理解,那么请问度娘:“操作符重载” 是什么吧

Trailing Closures
如果,闭包表达式作为函数的最后一个参数的时候,闭包表达式又很长,不能像上面写成那么短的形式的话,那么, 可以把闭包表达式,写在函数调用的外面,也就是()的后面,或者是下面:
func myCallClosure(closure: () -> ()) {           //参数是个函数,名字叫closure而已。。。
     closure()               //用参数的名字closure, 调用传入的函数
}

myCallClosure({})           //我只是放了个空闭包{}在这里
如果{}中的内容很长,我们可以把{}放在()外面:

myCallClosure() {

}
这个看起来非常像函数的定义了,但他不是,因为函数名前面没有 func关键字。
现在,我们回来看看sort,他的函数定义,也是最后一个参数要求传入函数,所以,我们可以把刚刚的sort,变成:

reversedNames = sort(names) { $0 > $1 }     //我觉得,连closure他爹都很难一眼看出来了。。。

我记得前几天,有群友,帖了这样一段代码,问:这是什么意思,今天,我才知道他一定是没有好好读手册!!!!!看下他帖的代码:


通过上面闭包的学习,现在是不是很容易看懂这个代码就是个闭包了? 只不过map后面没有写()
至于这个numbers.map是怎么用的(numbers是个Array,map是Array的方法),这段代码的详细意思,手册上都有非常详尽的讲解,我就不列在这里,也不要再问这个问题了,不要搞得好像自己学习能力很差一样。。。

Capturing Values
早些时候,提到了闭包可以捕捉并保存它的外层函数的常量和变量,接下来,我们来看看,有什么神奇的事情,如果上面的闭包内容都理解了之后,这一段,其实没啥好说的,直接上官方代码:


incrementor是个嵌套函数, 它返回了一个Int, 但是神奇的是,他没有传入参数,而是使用了一个叫作runningTotal的外层函数makeIncrementor定义的一个局部变量,当makeIncrementor返回incrementor函数的时候,实际上是返回了incrementor函数的一个复制出来的实体函数( 每次调用makeIncrementor的时候都会复制一个新的incrementor函数), 正因为闭包可以保存它外层函数定义的常量和变量,所以,当外层函数的作用域已经不存在的时候,它依然可以使用那个常量或变量的值:


看到每次调用 incrementByTen(),得到的结果在递增了吧? 


这一段,也证明了我上面的猜: 每次调用makeIncrementor的时候都会复制一个新的incrementor函数

闭包是引用类型


这里并没有调用 makeIncrementor去复制一个新的函数,而仅仅是定义了一个变量 alsoIncrementByTen 被赋值成了 incrementByTen //这个函数在上面的时候,已经被调用了多次,正因为闭包是引用类型,这里的alsoIncrementByTen实际上只是 incrementByTen 引用的那个之前复制出来的函数的新引用而已,于是,得到的结果是50.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值