正在学swift以及iOS,学习过程中发现swift语言中有许多精妙之处。上课时closure这个swift的精华和重要概念没讲清楚,去看了Stanford公开课受益匪浅,在此记下备忘。因为我之前的主要语言是Java,也站在一个Javaer的角度谈谈Swift中不同的概念。
Function Type
在说closure之前,首先说下swift中的function。swift中的function是一个type,也就是说可以作为一个变量存在。例如:
var operation : (Double) -> Double
这里声明了一个名为“operation”的变量,而他的类型是一个拥有一个Double参数,并返回一个Double值的function。既然是变量,就可以赋值,因此,你可以将任何一个“拥有一个Double参数,并返回一个Double值”的function赋值给这个“operation”变量。例如:
operation = sqrt
这样就把sqrt(平方根)这个函数赋值给了operation这个变量,现在我们就可以使用这个operation函数了,使用方法和正常的function相同:
let result = operation(2.25) // result will be 1.5
关于Closure
首先closure的概念, 可以说是一个没有名字的、in-line 的function,他的存在是为了简化代码的。比如,当你想实现一个简单的函数,并且这个函数仅在某个地方需要,以后不太可能会用到。这时你单独先一个function就很麻烦了,于是,closure站立了出来。举例来说,你想实现一个把double取负的function,没有closure时你可能会这么做:
func changeSign(num : Double) -> Double {return -Double}
let operation = changeSign
这里特意把changeSign这个函数写在一行,因为想展示从一个普通func怎么转化为closure。接下来我们试着直接把函数体赋值给operation这个变量从而简化代码:
let operation = (num : Double) -> Double {return -num} // syntax incorrect
这里直接把上面changeSign后面斜体的函数体搬了过来,然而此时语法还不对,需要再做点微调:
let operation = {(num : Double) -> Double in return -num} // this is a closure
注意上面代码中斜体并加粗的部分:1.把函数体的大括号移到最前面 2.用关键词in替换原来大括号的位置。此时,大括号包住了整合closure,大括号以及内部就是一个closure。注意关键词in,它并不表示英语里“in”的意思,而是可以看做分割该函数(closure也是一个函数)的变量与返回值(此处为(num : Double) -> Double,即该函数的类型)和函数体代码(此处为return -Double)的分界线,替代普通function中开大括号(open curly parenthesis)的作用。
以上就是一个最最基本、最最普通、最最“正经”的closure。然而我们并不满足,希望它更简化!首先,由于swift拥有type inference的能力,因而我们并不需要写明它的返回类型:
let operation = {(num : Double) in return -num}
还是因为type inference,我们也不需要写明输入参数的类型:
let operation = {(num) in return -num}
我们也知道,closure一定会返回一个值,所以return这个关键词也没必要写上了:
let operation = {(num) in -num}
想变量名是一件很麻烦的事,我们很懒所以并不想做这件事。closure允许你在函数体中用$0, $1, $2...分别代表第一个参数,第二个参数,第三个参数。由于我们连参数都没写,也就不需要in这个关键词了,只需要函数体即可:
let operation = { -$0 }
还想继续简化?-- 你咋不上天呢?
因为closure是function,而function是一种type,因而closure可以作为函数参数,这在swift中也很常见。例如:
High Order Function - Closure的妙用之一
Higher Order Function (以下简称HOF) 是 functional programming中的重要函数。Swift中的主要HOF有map, filter, reduce, flatmap等。
如果知道mapreduce,这些函数的作用应该不会陌生。Swift中的higher order function是Collection的函数,这些HOF的参数都是一个closure,返回值是一个Array。HOF会逐个作用于该collection中的每一个元素,并把该closure以该元素为参数的返回值append到返回的Array中。
map函数
map的作用是把collection中的元素变换成另一个元素,由于只是一个变换,因而返回的Array长度和原collection的元素数量相同。
let numbers = [1,6,2,15,2,8]
let negativeNumbers = numbers.map( {(element: Int) -> Int in return -element} ) // 返回 [-1,-6,-2,-15,-2,-8]
利用前面提到的简化,可以逐步简化:
let negativeNumbers = numbers.map( {element in -element} )
let negativeNumbers = numbers.map( { -$0 } )
上面提到,HOF会逐个作用于collection的每个元素,所以此处的$0就是collection中的一个元素,而closure的返回值-$0会被逐个添加到map函数返回的Array中。上面的代码同样也可以写成下面两种形式:
let negativeNumbers = numbers.map(){ -$0 }
let negativeNumbers = numbers.map{ -$0 }
上面这两种语法都对,因为swift允许:1. 当一个closure是函数的最后一个参数时,你可以把它移到小括号外面 2. 当closure是函数的唯一一个参数时,你连那个小括号也不需要了。由于规则1,我们也很常见swift中的函数的最后一个参数是一个closure。
filter函数
filter顾名思义,过滤掉collection中的一些元素。filter函数中的closure依然逐个作用于collection中的每一个元素,返回一个bool值。当该closure返回true时,该元素会被添加到filter函数返回的Array中,而若closure返回为false,则该元素不会被添加,也就是被“过滤“掉了。
let numbers = [1,6,2,15,2,8]
let oddNumbers = numbers.filter{ $0 == 2 } //返回 [6,2,2,8]
当一个元素$0符合 $0 == 2 的条件时,它会被添加到返回的array中。
reduce函数
reduce函数比前两个稍微复杂一点。它对collection中也是对所有元素逐个进行操作,但它的返回值只有一个,也就是说把所有元素reduce成一个。因此,在对元素进行操作后,它会把操作的结果记下来,在下一次调用该closure时(对下一个元素操作时)作为一个参数传入。因此,reduce函数的closure有两个参数,$0是前一次操作完的结果,$1是当前操作对象元素。同时,reduce函数本身也有两个参数,第一个参数是一个initial value,第二个参数是closure。由于closure对第一个元素操作时没有前一次操作的结果,所以需要这个initial value来作为closure对第一个对象操作时的$0
let numbers = [1,6,2,15,2,8]
let sum = numbers.reduce( initialValue : Int, { prevSum : Int, currElement : Int in return prevSum + currElement } ) //返回 34
对numbers[0]操作时,prevSum = initialValue, currElement = numbers[0], closure返回0 + 1 = 1, 并把返回值1赋值给prevSum: prevSum = 1。
对numbers[1]操作时, prevSum = 1, currElement = numbers[1], closure返回1 + 6 = 7。以此类推。
也可简写成:
let sum = numbers.reduce(0, {$0 + $1})
let sum = numbers.reduce(0) {$0 + $1}
还有一种比较变态的简化,因为reduce知道你要在$0和$1之间进行操作,所以你可以不用写他们俩,只写一个操作符:
let sum = numbers.reduce(0, +)