Swift 烧脑体操(二) - 函数的参数

前言

\\

Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性。这也使得我们学习掌握这门语言变得相对来说更加困难。不过一切都是值得的,Swift 相比 Objective-C,写出来的程序更安全、更简洁,最终能够提高我们的工作效率和质量。

\\

Swift 相关的学习资料已经很多,我想从另外一个角度来介绍它的一些特性,我把这个角度叫做「烧脑体操」。什么意思呢?就是我们专门挑一些比较费脑子的语言细节来学习。通过「烧脑」地思考,来达到对 Swift 语言的更加深入的理解。

\\

这是本体操的第二节,练习前请做好准备运动,保持头脑清醒。

\\

准备运动:基础知识

\\

d8c4cde5b04eda3269e6f3d9c85e7be6.jpg

\\

面向对象语言的世界观

\\

对于很多面向对象的编程语言来说,在思考问题时,总是把「对象」作为考虑问题的基本出发点。

\\

面向对象的程序设计通过以下三大规则,构建出程序设计的基础,它们是:

\\
  1. 封装(Encapsulation),将一个相对独立的逻辑涉及的变量和函数放到一个类中,然后对外暴露少量接口,使其高内聚,低耦合。\\t
  2. 继承(Inheritance),子类可以继承父类的变量和函数,并且可以修改或扩展父类的行为。\\t
  3. 多态(Polymorphism),父类的指针可以指向子类的实例,在运行时程序语言支持找到子类对应的函数实现。\

在以上三大准则的基础上,再引入一些设计原则,比如:

\\
  1. 单一职责原则(Single Responsibility),每个类只应该做一件事情。\\t
  2. 不要重复原则(Don't Repeat Yourself),相同(或相似)的代码不应该重复两次。\\t
  3. 好的组合优于继承(Better Composition over Inheritance),尽量使用组合而不是继承来设计。\

于是,程序世界就基于这些规则和原则,产生出了设计模式,进而能更加精准地指导我们的编程行为。这就像我们学习几何,先学习几条公理,然后以后的大量定理都通过公理证明而来。

\\

举个例子,单例模式(Singleton Pattern)其实就是封装和单一职责原则的产物。代理模式(Delegate Pattern) 也是单一职责和封装中的面向接口设计的思想的产物。

\\

但是,在面向对象语言的世界观里面,函数都是作为一个附属物存在的。函数通常附属于一个具体类的某个方法中。或许有一个函数它根本都不需要任何对象作为容器,为了这个世界的统一,我们还是会构造一个类,把这个函数放进去。比如,在小猿搜题中,我们就有一个叫 ImageUtils 的类,里面放了操作图像的各种各样的静态方法,有一些图象操作函数其实也不太通用,但是总得找一个类放不是。

\\

在一些面向对象语言的世界中,如果把对象称作 OOP 的一等公民的话,那么函数就是二等公民。

\\

函数式编程

\\

在 Swift 的世界中,函数并不是二等公民。是的,Swift 引入了大量函数式编程的特性,使得我们能够把函数当作一等公民来对待。

\\

一等公民有什么权利呢?那就是函数可以像对象一样,被赋值、被当作参数传递、参与计算或者当作结果被返回。

\\

我们先来看一段函数被赋值的例子,在下例中,我们将一个函数赋值给一个名为 myFunc 的变量,然后调用它。

\\
\let myFunc = { \    () -\u0026gt; String in\    return \"Tang Qiao\"\}\\let value = myFunc()\// value 的值为 \"Tang Qiao\"\
\\

我们再来看一个函数被当作运算结果返回的例子。在这个例子中,我们希望构造一个「加法器」工厂,这个工厂能够接受一个参数 addValue,返回一个加法器函数,这个加法器函数能够将传递的参数加 addValue 之后返回。以下是实现的代码:

\\
\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\    func adder(value: Int) -\u0026gt; Int {\        return addValue + value\    }\    return adder\}\
\\

有了上面这个函数,我们就可以构造一个 +2 的函数,然后使用它,如下所示:

\\
\let add2 = addFactory(2) // 构造一个 +2 的函数\let result = add2(3) // 运算,传入 3,得到 5\
\\

函数的参数

\\

但是在本次「烧脑体操」中,全面介绍函数式编程明显不太现实,所以我们仅从函数的参数来深入学习一下,看看在 Swift 语言中,函数的参数能够有多复杂。

\\

参数的省略

\\

我们先来简单看看函数参数的省略吧,因为有类型推导,函数的参数在 Swift 中常常可以被省略掉,特别以匿名函数(闭包)的形式存在的时候。

\\

我们来看一个数组排序的例子:

\\
\let array = [1, 3, 2, 4]\let res = array.sort {\    (a: Int, b: Int) -\u0026gt; Bool in\    return a \u0026lt; b\}\\
\\

如果一个函数返回类型可以通过推导出来,则返回类型可以省略。所以以上代码中的 -\u0026gt; Bool 可以删掉,变成:

\\
\let array = [1, 3, 2, 4]\let res = array.sort {\    (a: Int, b: Int) in\    return a \u0026lt; b\}\
\\

如果一个函数的参数类型可以推导出来,则参数的类型可以省略。所以以上代码中的 : Int 可以删掉,变成:

\\
\let array = [1, 3, 2, 4]\let res = array.sort {\    (a, b) in\    return a \u0026lt; b\}\\
\\

如果函数参数的个数可以推导出来,也可以不写参数。那怎么使用这些参数呢?可以用 $0, $1 这样的方式来引用参数。所以以上代码中的 (a, b) 可以删掉,因为这样的话,参数和返回值都省略了,所以in也可以省略了,变成:

\\
\let array = [1, 3, 2, 4]\let res = array.sort {\    return $0 \u0026lt; $1\}\
\\

Swift 还有一个规则,如果函数的 body 只有一行,则可以把 return 关键字省略了,所以以上代码可以进一步简化成:

\\
\let array = [1, 3, 2, 4]\let res = array.sort {\    $0 \u0026lt; $1\}\
\\

最后一个简化规则更加暴力,因为 \u0026lt; 符号也是一个函数,它接受的参数个数,类型和返回值与 sort 函数需要的一样,所以可以直接简化成:

\\
\let array = [1, 3, 2, 4]\let res = array.sort( \u0026lt; )\
\\

拿这个的方法,同样可以把我们刚刚写的 addFactory 做简化,最后简化成如下的形式:

\\
\// 简化前\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\    func adder(value: Int) -\u0026gt; Int {\        return addValue + value\    }\    return adder\}\// 简化后\func addFactory(addValue: Int) -\u0026gt; (Int -\u0026gt; Int) {\    return { addValue + $0 }\}\
\\

函数参数中的其它关键字

\\

有些时候,我们的函数接受的参数就是另外一个函数,例如 sort,map,所以我们在看代码的时候,需要具备熟悉这种写法的能力。

\\

我们来看看数组的 map 函数的定义吧:

\\
\public func map\u0026lt;T\u0026gt;(@noescape transform: (Self.Generator.Element) throws -\u0026gt; T) rethrows -\u0026gt; [T]\
\\

这个函数定义中出现了几个我们刚刚没提到的关键词,我们先学习一下。

\\
@noescape
\\

@noescape,这是一个从 Swift 1.2 引入的关键字,它是专门用于修饰函数闭包这种参数类型的,当出现这个参数时,它表示该闭包不会跳出这个函数调用的生命期:即函数调用完之后,这个闭包的生命期也结束了。以下是苹果的文档原文:

\\
\

A new @noescape attribute may be used on closure parameters to functions. This indicates that the parameter is only ever called (or passed as an @noescape parameter in a call), which means that it cannot outlive the lifetime of the call. This enables some minor performance optimizations, but more importantly disables the self. requirement in closure arguments.

\
\\

什么情况下一个闭包参数会跳出函数的生命期呢?很简单,我们在函数实现内,将一个闭包用 dispatch_async 嵌套,这样这个闭包就会在另外一个线程中存在,从而跳出了当前函数的生命期。这样做主要是可以帮助编译器做性能的优化。

\\

如果你对此感兴趣,这里有一些更详细的介绍供你学习:

\\
throwsrethrows
\\

throws 关键字表示:这个函数(闭包)可能抛出异常。而 rethrows 关键字表示:这个函数如果抛出异常,仅可能是因为传递给它的闭包的调用导致了异常。

\\

throws 关键字的存在大家都应该能理解,因为总有一些异常可能在设计的时候希望暴露给上层,throws 关键字的存在使得这种设计成为可能。

\\

那么为什么会有 rethrows 关键字呢?在我看来,这是为了简化很多代码的书写。因为一旦一个函数会抛出异常,按 Swift 类型安全的写法,我们就需要使用 try 语法。但是如果很多地方都需要写 try 的话,会造成代码非常啰嗦。 rethrows 关键字使得一些情况下,如果你传进去的闭包不会抛出异常,那么你的调用代码就不需要写 try。

\\

如果你对此感兴趣,这里有一些更详细的介绍供你学习:

\\

函数作为函数的参数

\\

刚刚说到,函数作为一等公民,意味着函数可以像对象一样,被当作参数传递或者被当作值返回。对此,我们专门有一个名称来称呼它,叫做高阶函数(higher-order function)

\\

在刚刚那个数组的 map 函数中,我们就看到了它接着另外一个函数作为参数,这个函数接受数组元素类型作为参数,返回一个新类型。

\\
\public func map\u0026lt;T\u0026gt;(@noescape transform: (Self.Generator.Element) throws -\u0026gt; T) rethrows -\u0026gt; [T]\
\\

有了 map 函数,我们就可以轻松做数组元素的变换了。如下所示:

\\
\let arr = [1, 2, 4]\// arr = [1, 2, 4]\\let brr = arr.map {\    \"No.\" + String($0)\}\// brr = [\"No.1\
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值