1.概念

Swift 中的闭包是自包含的代码块,可以在代码中传递和使用。它们可以捕获和存储其上下文中的任何常量和变量引用。

2.表达式语法

闭包的完整形式为:

{ (parameters) -> returnType in
    statements
}

这里,参数列表和返回类型可选。例如,一个没有参数和返回值的简单闭包示例:

let greet = {
    print("Hello, world!")
}

greet()

当我们需要向闭包传递参数时,可以在参数列表中定义它们:

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

let sum = add(5, 7)
print(sum)  // 输出:12

将闭包作为参数传递

闭包可以像普通变量一样传递给函数。以下是一个接受简单闭包作为参数的函数:

func perform(operation: () -> Void) {
    operation()
}

perform {
    print("Performing an operation.")
}

闭包简写

在 Swift 中,当闭包作为参数传递给函数时,可以利用语言特性对闭包进行简写。以下列举了闭包简写的几种方法:

  1. 参数类型推断:当闭包作为函数的参数时,Swift 会自动推断闭包参数的类型。因此,在闭包表达式中,无需明确指定参数类型。例如:


func doMath(_ operation: (Int, Int) -> Int) {
    let result = operation(5, 3)
    print(result)
}

doMath { a, b in
    a * b
} // 输出:15

在上述示例中,我们可以省略闭包中的参数类型 Int,因为 Swift 根据 doMath 函数类型推断出闭包参数的类型。

  1. 隐式返回:针对只包含一个表达式的闭包,可以不使用 return 关键字,因为这个表达式会作为闭包的“隐式返回值”。例如:


let sortedNumbers = [5, 2, 1, 8, 4, 7].sorted { a, b in
    a < b
}

在这个示例中,我们省略了 return 关键字,因为闭包只包含一个表达式:a < b。Swift 将此表达式的结果视为隐式返回值。

  1. 简写参数名:当闭包参数的名字未定义时,可以使用 $0, $1, $2 等编号引用闭包参数。例如:


let sortedNumbers = [5, 2, 1, 8, 4, 7].sorted { $0 < $1 }

在这个示例中,我们使用简写参数名 $0$1 表示闭包的第一个和第二个参数。这样就不需要显式地定义参数名(例如 a, b 等)。

  1. 尾随闭包(Trailing closure):当函数的最后一个参数是一个闭包时,可以将闭包表达式移出函数括号,并跟在函数参数列表之后。这种写法在闭包表达式较长或者闭包作为主要逻辑参数时,使代码更具可读性。例如:

示例 1. 使用 map 函数


let numbers = [1, 2, 3, 4, 5]

// 使用尾随闭包语法
let squaredNumbers = numbers.map { $0 * $0 }

// 标准闭包语法
let squaredNumbersStandard = numbers.map({ $0 * $0 })

在这个示例中,使用尾随闭包语法让代码看起来更简洁。map 函数的最后一个参数是一个闭包,所以我们可以将闭包移到括号外部。

示例 2. 使用 async 函数


DispatchQueue.main.async {
    print("Hello, trailing closure!")
}

// 标准闭包语法
DispatchQueue.main.async(execute: {
    print("Hello, standard closure!")
})

在这个示例中,async 函数的最后一个参数也是一个闭包,我们将其移出参数列表,使用尾随闭包简化代码。

逃逸闭包(Escaping Closures)

逃逸闭包(escaping closure)是 Swift 中闭包的特殊类型,它指的是传递给函数的闭包会在函数返回后执行。


3.注意事项

逃逸闭包可能导致循环引用(retain cycle)问题。当闭包在函数之外执行,尤其是在闭包和类实例之间产生相互引用时,需要特别关注内存管理。这时,应使用捕获列表(capture list),指定捕获方式为 weakunowned