Swift 闭包

492次阅读  |  发布于10月以前

欢迎您阅读 Mastering Swift 基础教程,本文我们将介绍 Swift 中闭包表达式、尾随闭包和逃逸闭包等相关的内容。如果你尚未安装 Xcode 和配置 Swift 开发环境,请您先阅读这篇文章。

接下来,我们启动 Xcode,然后选择 "File" > "New" > "Playground"。创建一个新的 Playground 并命名为 "Closures"。

在 Swift 中,闭包是自包含的功能块,可以捕获和存储上下文中任意常量和变量的引用。Swift 闭包有多种形式,包括闭包表达式、尾随闭包、逃逸闭包等。

闭包表达式

闭包表达式语法

闭包表达式是一种轻量级语法,用于表示内联闭包。它的语法如下:

{ (parameters) -> returnType in
    // Closure body
}

相关说明如下:

创建闭包

了解了闭包表达式的语法之后,我们来创建一个闭包。

Swift Code

// 定义一个闭包,它接受两个参数并返回它们的总和
let addClosure: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// 调用 addClosure 闭包
let sum = addClosure(2, 3)
print("Sum: \(sum)")

// Output: Sum: 5

在以上例子中,addClosure 是一个接受两个整数参数并返回它们和的闭包。在闭包表达式中,Swift 可以根据上下文推断参数类型和返回类型,因此通常可以省略它们:

let addClosure: (Int, Int) -> Int = { a, b in
    return a + b
}

如果闭包体只包含一条语句,可以省略 return 关键字:

let addClosure: (Int, Int) -> Int = { a, b in a + b }

在 Swift 的闭包表达式中,$0$1 等是用来表示闭包参数的缩写形式。这种缩写形式允许在闭包表达式中直接引用参数,而不需要显式地命名。所以,以上的代码,还可以继续简化:

let addClosure: (Int, Int) -> Int = { $0 + $1 }

下面,我们来看一下等价的 TypeScript 代码。

TypeScript Code

const addClosure: (a: number, b: number) => number = (a, b) => {
    return a + b;
};

const sum: number = addClosure(2, 3);
console.log(`Sum: ${sum}`); 

// Output: "Sum: 5"

闭包作为函数参数

闭包可以作为函数的参数,使得函数更加灵活。

Swift Code

func performOperation(_ operation: (Int, Int) -> Int, a: Int, b: Int) {
    let result = operation(a, b)
    print("Result: \(result)")
}

performOperation({ (a: Int, b: Int) -> Int in
    return a + b
}, a: 2, b: 3)

// Output:Result: 5

TypeScript Code

function performOperation(operation: (a: number, b: number) => number, a: number, b: number): void {
    const result: number = operation(a, b);
    console.log(`Result: ${result}`);
}

performOperation((a: number, b: number) => {
    return a + b;
}, 2, 3);

// Output: "Result: 5"

闭包作为排序函数的参数

Swift Code

let numbers = [4, 2, 8, 5, 1]
// 使用尾随闭包
let sortedNumbers = numbers.sorted { $0 < $1 }
print(sortedNumbers)

// Output: [1, 2, 4, 5, 8]

TypeScript Code

const numbers: number[] = [4, 2, 8, 5, 1];
const sortedNumbers: number[] = numbers.sort((a, b) => a - b);

console.log(sortedNumbers);
// Output: [1, 2, 4, 5, 8]

在闭包中捕获值

在 Swift 中,闭包可以捕获并存储它们定义时所在上下文中的常量和变量。即使定义这些常量和变量的原始上下文已经不存在,闭包仍然可以引用和修改这些值。下面我们来举一个闭包捕获外部函数的局部变量的示例:

Swift Code

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    let incrementer: () -> Int = {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // Output: 10
print(incrementByTen()) // Output: 20

在以上代码中,incrementByTen 是一个闭包,它捕获了 makeIncrementer 函数内的 runningTotalamount 变量。即使 makeIncrementer 函数的执行已经完成,这些捕获的变量仍然存在于闭包内。

在 JavaScript 中,函数可以访问定义它们的函数的作用域中的变量。这个特性被称为词法作用域或静态作用域。虽然 JavaScript 没有专门的闭包语法,但函数本身就表现出闭包的行为。

TypeScript Code

function makeIncrementer(amount: number): () => number {
    let runningTotal = 0;
    return function(): number {
        runningTotal += amount;
        return runningTotal;
    }
}

const incrementByTen = makeIncrementer(10);
console.log(incrementByTen()); // Output: 10
console.log(incrementByTen()); // Output: 20

尾随闭包

尾随闭包(Trailing Closures)在 Swift 中是一个非常有用的特性,尤其是当闭包作为函数的最后一个参数时。尾随闭包语法使得在函数调用时将闭包写在函数括号之外,使得代码更加清晰。

let result = someFunction(arg1, arg2) { parameter in
    // 闭包体
}

Swift Code

func applyOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

// Calling a function using a trailing closure
let result = applyOperation(2, 3) { $0 + $1 }
print("Result: \(result)") 

// Output: Result: 5

在以上示例中,applyOperation 函数接受两个整数和一个闭包作为参数。使用尾随闭包语法,我们可以将闭包写在函数调用的括号之外,使得代码更加简洁。在 TypeScript 中,并没有直接的尾随闭包语法,但可以通过将函数的最后一个参数定义为函数类型来达到类似的效果。

TypeScript Code

function applyOperation(a: number, b: number, operation: (a: number, b: number) => number): number {
    return operation(a, b);
}

const result: number = applyOperation(2, 3, (a, b) => a + b);
console.log(`Result: ${result}`);

// Output: "Result: 5"

逃逸闭包

逃逸闭包(Escaping Closures)是一个重要的概念,特别是在处理异步操作和回调时。逃逸闭包与非逃逸闭包的主要区别在于它们的生命周期:逃逸闭包可以在函数返回之后被调用,而非逃逸闭包则必须在函数返回之前被调用。逃逸闭包是在函数执行完毕后才被调用的闭包。在参数列表前加上 @escaping 关键字表示该闭包逃逸。

Swift Code

import Foundation

func loadData(completionHandler: @escaping (String) -> Void) {
    // 模拟异步操作
    DispatchQueue.global().async {
        let data = "Hello, Escaping closures!"
        DispatchQueue.main.async {
            completionHandler(data)
        }
    }
}

loadData { data in
    print("Received: \(data)")
}

// Output: Received: Hello, Escaping closures!

在以上示例中,completionHandler 是一个逃逸闭包,因为它在函数返回之后的某个时间点被调用。在 TypeScript 中,虽然并没有专门的逃逸闭包的语法,但我们可以通过传递函数作为参数,并在异步操作完成后调用该函数来模拟逃逸闭包的效果。

TypeScript Code

function loadData(): Promise<string> {
    return new Promise((resolve) => {
        // 模拟异步操作
        setTimeout(() => {
            const data = "Hello, Closures!";
            resolve(data);
        }, 1000);
    });
}

loadData().then(data => {
    console.log(`Received: ${data}`);
});

// Output: "Received: Hello, Closures!"

Swift 的逃逸闭包和 TypeScript 的异步操作处理都是处理延时执行、异步执行的强大工具。它们都允许函数在完成某些操作后再执行一些代码,这在处理网络请求、数据库操作等异步任务时非常有用。

本文我们介绍了 Swift 中闭包表达式、尾随闭包和逃逸闭包等相关的内容。通过与 TypeScript 语法的对比,希望能帮助您更好地理解 Swift 的相关特性。

如果你也想学习 Swift,欢迎跟我一起交流【回复 Swift】,近期我将持续更新一些 Swift 和 SwiftUI 的相关教程。

如果你想深入学习 TypeScript,推荐你阅读我的《重学 TS 3.0》系列教程。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8