Swift相关的学习资料已经很多,我想从另外一个角度来介绍它的一些特性,我把这个角度叫做「烧脑体操」。什么意思呢?就是我们专门挑一些比较费脑子的语言细节来学习。通过「烧脑」地思考,来达到对Swift语言的更加深入的理解。
这是本体操的第三节,练习前请做好准备运动,保持头脑清醒。
那为什么本文的标题是「美女的Swift体操」呢?唉,还不是因为前两篇阅读量太低!于是我只好用美女图片吸引大家进来阅读了。你看我容易么?
文末有Swift的真实面试题,建议大家还是认真读读,万一找工作用上了呢!
准备运动:基础知识在上一节里面,我们其实已经涉及到了高阶函数了。在Wikipedia中,是这么定义高阶函数(higher-orderfunction)的,如果一个函数:
接受一个或多个函数当作参数
把一个函数当作返回值
那么这个函数就被称作高阶函数。下面是一个简单的排序的例子,在这个例子中,传进去的参数就是一个函数:
letnumbers=[1,4,2,3]letres=numbers.sort{$0$1}TrailingClosureSyntax
上面的代码看着不像是函数作为参数存在,这是因为Swift的TrailingClosure特性。Swift允许当函数的最后一个参数是闭包的时候,以紧跟{}的形式,将最后一个闭包的内容附加在函数后面。
所以,以下两行代码是等价的:
//正常写法,函数是作为sort的参数arr.sort({$0$1})//TrailingClosure写法,更简洁明了arr.sort{$0$1}常见用法示例
高阶函数在Swift语言中有大量的使用场景,我们先来看一看常见的用法:
遍历我们可以用map方法来对数组元素进行某种规则的转换,例如:
letarr=[1,2,4]//arr=[1,2,4]letbrr=arr.map{No.+String($0)}//brr=[No.1,No.2,No.4]求和
我们可以用reduce方法,来对数组元素进行某种规则的求和(不一定是加和)。
letarr=[1,2,4]//arr=[1,2,4]letbrr=arr.reduce(0){(prevSum:Int,element:Int)inreturnprevSum+element}//brr=7letcrr=arr.reduce(){if$0=={returnString($1)}else{return$0++String($1)}}//crr=筛选
我们可以利用filter方法,来对数组元素进行某种规则的过滤,例如:
letarr=[1,2,4]//arr=[1,2,4]letbrr=arr.filter{$0%2==0}//brr=[2,4]遍历
即使是以前最简单的遍历,我们也可以用高阶函数的写法,将遍历需要的操作,以函数参数的形式传入forEach方法中,例如:
letarr=[1,2,4]arr.forEach{print($0)}烧脑体操
下面我们来看看高阶函数一些比较烧脑的细节。
用高阶函数来隐藏私有变量高阶函数使得代码逻辑可以用函数为主体来进行封装,下面我将详细解释一下这句话。
在面向对象的世界里,逻辑存在的基本单元是对象,每个对象代表着一个最小可复用模块。在对象的内部,由高内聚的成员变量和成员函数构成。这些函数相互调用,并且操作对象的内部成员变量,最终对外产生可预期的行为。
但是利用高阶函数,我们可以同样做到与对象类似的,高内聚的成员变量和成员函数,下面我就举一个具体的例子。
下面的代码中,我们用类的方式,实现了一个Clock类,Clock类实现了一个getCount方法,每次调用的时候返回的值+1。为了测试代码,我们定义了两个实例c1和c2,它们都可以正常输出预期的值。
classClock{varcount:Int=0funcgetCount()-Int{return++count;}}letc1=Clock()c1.getCount()//得到1c1.getCount()//得到2letc2=Clock()c2.getCount()//得到1
那么接下来,我们用高阶函数的方式,来做一下同样的事情。我们先看代码:
funcgetClock()-()-Int{varcount:Int=0letgetCount={()-Intin++count;}returngetCount}letc1=getClock()c1()//得到1c1()//得到2letc2=getClock()c2()//得到1
在上面的代码中,我们这里定义了一个getClock函数,这个函数可以返回一个getCount函数。然后,不太一样的地方是,这个getCount函数持有了一个外部的变量count。于是,这个函数也变得有了状态(或者你也可以说它有了SideEffect)。每次调用这个函数的时候,返回的值都会变化。
另一方面,因为count变量是getClock这个高阶函数的内部变量,所以它并没有像全局变量一样使得封装性被打破。getClock函数仍然可以看作一个高内部的可复用模块,并且对外隐藏了实现细节。
所以,Swift语言的高阶函数以及闭包可以capture外部变量的特性,使得代码逻辑可以以函数作为主体来进行封装,这将使得我们的代码组织更加灵活。
当然,如果滥用,这也会造成代码组织变得更加混乱。
面试题另一个烧脑的故事是来自于一个朋友的面试题。在面试中,面试官要求他用数组的reduce方法实现map的功能。
这个题目实在是非常蛋疼,不过用来烧脑倒是不错,大家感兴趣的话可以先想想,再翻下面的参考答案。
letarr=[1,3,2]letres=arr.reduce([]){(a:[Int],element:Int)-[Int]invart=Array(a)t.append(element*2)returnt}//res=[2,6,4]
不过说回来,虽然这道题目有些奇怪,但是它确实考查了对于高阶函数灵活使用以及对reduce方法的理解。大家还可以试试这些题目:
问题一:用reduce方法找出数组中的最大值。
问题二:用reduce方法一次求出数组中奇数的和、以及偶数乘积。
以下代码是刚刚问题二的参考答案:
letarr=[1,3,2,4]letres:(Int,Int)=arr.reduce((0,1)){(a:(Int,Int),element:Int)-(Int,Int)inifelement%2==0{return(a.0,a.1*element)}else{return(a.0+element,a.1)}}//res=(4,8)
高阶函数另一个魔力就是可以链式调用,大家可以尝试这么一道题目:求一个数组中偶数的平方和。
以下是参考答案:
letarr=[1,3,2,4]letres=arr.filter{$0%2==1}.map{$0*$0}.reduce(0){$0+$1}总结
总结一下本次烧脑锻炼到的脑细胞:
学习了Swift语言中的一些使用高阶函数的示例,包括map,reduce,filter等。
学习了利用高阶函数来构造以函数为主体的功能模块。
练习了一些奇怪的面试题。
全文完。本文首发于InfoQ