函数与闭包

温馨提示:本文最后更新于2024-07-31 17:47:30,某些文章具有时效性,若有错误或已失效,请在下方留言

可变函数

可变函数是一种不确定迭代的函数,也就是你发送多少参数,它就接受多少参数。

func add(numbers: Int...) -> Int {
    var total = 0

    for number in numbers {
        total += number
    }

    return total
}

add(numbers: 1, 2, 3, 4, 5)

运算符重载

运算符重载是一种实现自己的运算符,甚至调整现有运算符(如+*)的能力。

操作符的基础

操作符的重载是使用函数来完成的。

func ==(lhs: MyTypeA, rhs: MyTypeB) -> Bool {
    return false
}

if MyTypeA() == MyTypeB() {
    print("Match!")
} else {
    print("No match!")
}

函数的名称就是运算符本身,即func ==,因此你要修改的内容一目了然。 该函数希望接收两个类型(lhsrhs,表示左手边和右手边),并返回一个布尔值,报告它们是否相等。

添加到现有运算符

了解了运算符的工作原理,让我们来修改*运算符,使它能像这样对整数数组进行乘法运算:

func *(lhs: [Int], rhs: [Int]) -> [Int] {
    guard lhs.count == rhs.count else { return lhs }
    
    var result = [Int]()
    
    for (index, int) in lhs.enumerated() {
        result.append(int * rhs[index])
    }
    
    return result
}

因为*运算符已经存在,所以重要的是lhsrhs参数,这两个参数都是整数数组:当两个整数数组相乘时,这些参数将确保这个新函数被选中。

添加新的运算符

添加新运算符时,需要向 Swift 提供足够的信息才能使用它。

  1. 必须指定运算符的位置: prefix(前缀)postfix(后缀) 或者 infix(中缀)
  2. 但如果不指定优先级或关联性,Swift 将提供默认值,使其成为低优先级的非关联运算符。
import Foundation

infix operator **

func **(lhs: Double, rhs: Double) -> Double {
    return pow(lhs, rhs)
}

let result = 2 ** 4 // 16 

这样的表达方式是行不通的

let result = 4 ** 3 ** 2
let result = 2 ** 3 + 2

这是因为我们使用了默认的优先级和关联性。要解决这个问题,我们需要决定**与其他运算符相比的优先级。

import Foundation

precedencegroup ExponentiationPrecedence {
    higherThan: MultiplicationPrecedence
    associativity: right
}

infix operator **: ExponentiationPrecedence

func **(lhs: Double, rhs: Double) -> Double {
    return pow(lhs, rhs)
}

let result = 2 ** 3 + 2 // 10

修改现有操作符

precedencegroup RangeFormationPrecedence {
    associativity: left
    higherThan: CastingPrecedence
}

infix operator  ... : RangeFormationPrecedence

func ...(lhs: CountableClosedRange<Int>, rhs: Int) -> [Int] {
    let downwards = (rhs ..< lhs.upperBound).reversed()
    return Array(lhs) + downwards
}

let range = 1...10...1
print(range)

闭包

闭包是 Swift 中的常见元素:全局函数是闭包,嵌套函数是闭包,函数方法(如sort()和map())接受闭包,懒属性使用闭包。

简单闭包

let greetPerson = {
    print("Hello there!")
}

greetPerson()

由于闭包是一等数据类型,即与整数、字符串和其他数据类型一样,因此可以复制它们,并将它们用作其他函数的参数。

let greetCopy = greetPerson
greetCopy()

在复制闭包时,请记住闭包是一种引用类型–这两个 “副本 “实际上都指向同一个共享闭包。

闭包接受参数,将参数列表写入闭包的大括号中,并在后面加上关键字in

let greetPerson = { (name: String) in
    print("Hello, \(name)!")
}

greetPerson("Taylor")

如果需要,这里是指定捕获列表的地方。最常用的方法是将self设为unowned,以避免强引用循环。

let greetPerson = { [unowned self] (name: String) in
    print("Hello, \(name)!")
}

greetPerson("Taylor")

闭包捕获

当两个变量指向同一个闭包时,它们都使用相同的捕获数据。当闭包引用一个值时,它需要确保该值在闭包运行时仍然存在。这个过程被称为捕获,它允许闭包引用和修改它所引用的值,即使原始值已不复存在。

func testCapture() -> () -> Void {
    var counter = 0

    return {
        counter += 1
        print("Counter is now \(counter)")
    }
}

let greetPerson = testCapture()
greetPerson() // 1
greetPerson() // 2
greetPerson() // 3

let greetCopy = greetPerson
greetCopy() // 4
greetPerson() // 5
greetCopy() // 6

闭包速记语法

let names = ["Michael Jackson", "Taylor Swift", "Michael Caine", "Adele Adkins", "Michael Jordan"]

let result1 = names.filter({ (name: String) -> Bool in
    if name.hasPrefix("Michael") {
        return true
    } else {
        return false
    }
})

print(result1.count)

Swift 知道传给filter()的闭包必须接受一个字符串并返回一个布尔值,因此我们可以删除它,只使用一个变量的名称,该变量将用于过滤每个项目。

let result2 = names.filter({ name in
    if name.hasPrefix("Michael") {
        return true
    } else {
        return false
    }
})

接下来,可以直接返回hasPrefix()的结果

let result3 = names.filter({ name in
    return name.hasPrefix("Michael")
})

尾随闭包允许去掉一组括号

let result4 = names.filter { name in
    return name.hasPrefix("Michael")
}

Swift 知道闭包必须返回一个布尔值,而且因为只有一行代码,所以 Swift 知道它必须是返回值的那一行, return 关键字可以省略。

let result5 = names.filter { name in
    name.hasPrefix("Michael")
}

当调用闭包时,Swift 会自动创建匿名参数名,由一个美元符号和一个从 0 开始向上的数字组成,比如 $0,$1,$2 …。这些速记参数名与闭包接受的参数相对应。你不能将显式参数和匿名参数混用。

let result6 = names.filter { name in
    name.hasPrefix("Michael")
}

let result7 = names.filter {
    $0.hasPrefix("Michael")
}

如果使用速记名称,通常会把整个方法调用放在一行中。

let result8 = names.filter { $0.hasPrefix("Michael") }

函数作为闭包

import Foundation
let words = ["1989", "Fearless", "Red"]
let input = "My favorite album is Fearless"

words.contains(where: input.contains)

contains(where:)会对数组中的每个元素都调用一次闭包,直到找到一个返回 true 的元素。将input.contains 传递给它意味着 Swift 将调用 input.contains(”1989″)并返回 false,然后调用input.contains(”Fearless”)并返回 true,然后就此停止。

let numbers = [1, 3, 5, 7, 9]

numbers.reduce(0) { (int1, int2) -> Int in
    return int1 + int2
}

代码运行时,将起始值和 1 相加产生 1,然后是 1 和 3(运行总数:4),然后是 4 和 5(9),然后是 9 和 7(16),然后是 16 和 9,总共产生 25。

let numbers = [1, 3, 5, 7, 9]
let result = numbers.reduce(0, +)

+是一个接受两个整数并返回其和的函数,因此我们可以去掉整个闭包,代之以一个运算符。

逃逸闭包

当闭包传递到函数中时,Swift 默认将其视为非缺省值。这意味着闭包必须立即在函数中使用,不能存储起来留待以后使用。逃逸闭包将会在方法返回后被调用,这些闭包存在于许多需要异步调用闭包的地方。

var queuedClosures: [() -> Void] = []

// 传入的闭包可以在以后使用,在本例中就是在调用executeQueuedClosures()函数时使用。
@MainActor func queueClosure(_ closure: @escaping () -> Void) {
    queuedClosures.append(closure)
}

@MainActor func executeQueuedClosures() {
    for closure in queuedClosures {
        closure()
    }
}

executeQueuedClosures()

@autoclosure

调用使用@autoclosure的函数很常见,但使用它编写函数却不常见。使用该属性时,它会根据你传入的表达式自动创建一个闭包。当你调用一个使用此属性的函数时,你写的代码并不是一个闭包,而是变成了一个闭包。

func printTest(_ result: () -> Void) {
    print("Before")
    result()
    print("After")
}

printTest({ print("Hello") })

这段代码创建了一个printTest()方法,该方法接受一个闭包并调用它。正如你所看到的,print(“Hello”)位于闭包内部,在 “Before “和 “After “之间被调用,因此最终输出是 “Before”、”Hello”、”After”。

如果改用@autoclosure,我们就可以重写printTest()调用,这样就不需要大括号了。

func printTest(_ result: @autoclosure () -> Void) {
    print("Before")
    result()
    print("After")
}

printTest(print("Hello"))

由于使用了@autoclosure,这两段代码产生了完全相同的结果。在第二个代码示例中,print(“Hello”)不会立即执行,因为它被包裹在闭包中,稍后再执行。

~= 运算符

~=是模式匹配运算符

let range = 1...100
let i = 42

if range ~= i {
    print("Match!")
}

不需要运算符,因为可以使用 ranges 的内置contains()方法进行编码。不过,与contains()相比,它在语法上确实有一点优势,因为它不需要额外的括号:

let test1 = (1...100).contains(42)
let test2 = 1...100 ~= 42
© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容