Swift中,一个标准的函数创建是这样的:

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

值得注意的是,在调用函数传递参数的时候,我们不光提供了参数的,同时也提供了参数的,这样确实牺牲了一点开发效率,但是提升了不少代码的可读性,总的来说肯定是利大于弊的。不过即使我们提供了参数的,传递参数的顺序仍然不可更改,在上面的例子中,如果写成greet(day: "Tuesday",person: "Bob")是不可以的。

当然也有一些情况,不传递参数其实也不影响可读性,比如最常见的print方法,显而易见它的第一参数都是要输出打印的东西,如果我们画蛇添足,明明可以print("Hello Word!")的,却非要写成print(items: "Hello Word!")就会显得过于鸡肋了。所以Swift提供一种方式,可以声明这个参数在调用时不需要提供,就像这样:

func greet(_ person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("Bob", day: "Tuesday")

在声明函数的时候,在其参数名之前通过_标记一下,即可让这个参数在调用时免去提供参数名的麻烦。并且需要被_的参数,没有顺序要求,像下面这样的代码也是可以的:

func greet(person: String, _ day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob","Tuesday")

这里还有一个不怎么常用的小技巧,就是_的位置也可以替换成其他内容,从而对这个参数起一个别名,比如这样:

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

函数的返回值是通过->标记的,这点在上面的例子上也已经看到了。如果需要返回多个值的话,可以使用Tuple(元组类型),比如这样:

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)

上面的函数接收一组整型数据作为参数,并计算出这组数据的最大值、最小值以及总和,把这些结果通过封装在在元组中返回,以达到返回多值的效果。

在一个函数内部还可以继续定义函数,并且遵循了大家已知的闭包作用域的规范:

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

Swift中的函数,是遵循first-class原则,也是一等公民,跟其他数据类型一样享受同样的待遇,当然也可以作为一个函数的返回值,比如:

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

我们可以通过print(increment.self)查看increment变量的类型,得到的结果会是(Function)

函数既然能被用作返回值,当然也可以当作参数进行传递,比如:

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

像上面这些把一个函数看成传统类型,一会当返回值一会当参数的玩法在其他很多语言里都是有的。对初学者来说,可能理解起来会稍有困难,主要还是因为实践的少,遇到的场景不多。我觉得没关系,你只要留个印象,以后在阅读别人代码的时候,如果遇到了,自己花点时间能读懂就行了。至于融会贯通、学以致用,可以慢慢来,随着经验的积累自然就会好的。

下面我们再来谈谈Closures(闭包),首先我们准备一个数组,就用前面的那个例子:

var numbers = [20, 19, 7, 12]

如果我想对数组中的每个元素进行一次平方运算,然后获得一个新的数组,首先我需要准备一个能把Int平方的函数:

func square(number: Int) -> Int{
    return number * number
}

而数组提供了一个叫map的函数,可以让我们方便地对其元素做逐个运算,并把运算结果汇集到新的数组中。所以我们只要对numbers进行如下的运算就可以了:

numbers.map(square)

这时候我们得到的结果就是:

[400, 361, 49, 144]

但其实我们可以把声明square和调用map合在一起写,就像这样:

numbers.map({ (number: Int) -> Int in
    return number * number
})

如你所见,声明专门的square函数或者是用闭包的方式来给map传递参数都是等价的。而后者的魅力在于可以省略掉一些已知的东西,进而简化成这样:

numbers.map({number in number * number})

这里,我们简化了入参的类型,return关键字,其实我们还可以更进一步,就连参数number也可以用内嵌的参数名$0替代:

numbers.map({$0 * $0})

当然,如果你的闭包是接收多个参数的,就可以在$0之后继续通过$1$2...获取其他的参数。比如,如果我们要对numbers排序,就需要提供一个能接收两个参数的闭包,最后写成这样:

numbers.sorted { $0 > $1 }

运算之后的结果为:

[20, 19, 12, 7]

如果你仔细观察sortedmap的代码,会发现在sorted调用闭包的时候,我们省略了闭包外面的(),这是因为如果函数调用只有一个参数,并且这个参数是个闭包时,为了简化输入,可以省略函数调用的()

关于函数和闭包就先介绍到这里了,我们下次见。

阅读相关文章: