7.闭包

闭包

闭包表达式

在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数。

基本语法

swift 复制代码
{
    (参数列表) -> 返回值类型 in
    函数体代码
}

示例对比

普通函数定义:

swift 复制代码
func sum(_ v1: Int, _ v2: Int) -> Int { 
    v1 + v2 
}

闭包表达式定义:

swift 复制代码
var fn = {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}
fn(10, 20)

直接调用闭包表达式:

swift 复制代码
{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(10, 20)

闭包表达式的简写

swift 复制代码
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

简写过程

1. 完整形式:

swift 复制代码
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})

2. 省略参数类型:

swift 复制代码
exec(v1: 10, v2: 20, fn: {
    v1, v2 in 
    return v1 + v2
})

3. 省略return关键字:

swift 复制代码
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2
})

4. 使用参数名简写:

swift 复制代码
exec(v1: 10, v2: 20, fn: { $0 + $1 })

5. 使用运算符:

swift 复制代码
exec(v1: 10, v2: 20, fn: +)

尾随闭包

如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。

尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式。

基本用法

swift 复制代码
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

exec(v1: 10, v2: 20) {
    $0 + $1
}

唯一实参的情况

如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号。

swift 复制代码
func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }

示例 - 数组的排序

Array的sort方法

swift 复制代码
func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
/// 返回true: i1排在i2前面
/// 返回false: i1排在i2后面

各种写法示例

swift 复制代码
var nums = [11, 2, 18, 6, 5, 68, 45]

// 使用普通函数
func cmp(i1: Int, i2: Int) -> Bool {
    // 大的排在前面
    return i1 > i2
}
nums.sort(by: cmp)  // [68, 45, 18, 11, 6, 5, 2]

// 使用完整闭包表达式
nums.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})

// 简化参数类型
nums.sort(by: { i1, i2 in return i1 < i2 })

// 简化return
nums.sort(by: { i1, i2 in i1 < i2 })

// 使用参数名简写
nums.sort(by: { $0 < $1 })

// 使用运算符
nums.sort(by: <)

// 使用尾随闭包
nums.sort() { $0 < $1 }
nums.sort { $0 < $1 }

// 结果:[2, 5, 6, 11, 18, 45, 68]

忽略参数

当闭包的参数不需要使用时,可以用下划线_来忽略。

swift 复制代码
func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

exec { _, _ in 10 }  // 10

闭包概念

定义

网上有各种关于闭包的定义,个人觉得比较严谨的定义是:

一个函数和它所捕获的变量/常量环境组合起来,称为闭包。

  • 一般指定义在函数内部的函数
  • 一般它捕获的是外层函数的局部变量/常量

闭包示例

swift 复制代码
typealias Fn = (Int) -> Int

func getFn() -> Fn {
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
}

// 返回的plus和num形成了闭包
var fn1 = getFn()
var fn2 = getFn()

fn1(1) // 1
fn2(2) // 2
fn1(3) // 4
fn2(4) // 6
fn1(5) // 9
fn2(6) // 12

闭包的本质

可以把闭包想象成是一个类的实例对象:

  • 内存在堆空间
  • 捕获的局部变量/常量就是对象的成员(存储属性)
  • 组成闭包的函数就是类内部定义的方法
swift 复制代码
class Closure {
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
}

var cs1 = Closure()
var cs2 = Closure()

cs1.plus(1) // 1
cs2.plus(2) // 2
cs1.plus(3) // 4
cs2.plus(4) // 6
cs1.plus(5) // 9
cs2.plus(6) // 12

闭包表达式简写

swift 复制代码
func getFn() -> Fn {
    var num = 0
    return {
        num += $0
        return num
    }
}

思考题

思考:如果num是全局变量呢?

如果num是全局变量,那么就不存在捕获外层函数局部变量的情况,严格来说就不是闭包了。


练习

练习1:多个闭包共享变量

swift 复制代码
typealias Fn = (Int) -> (Int, Int)

func getFns() -> (Fn, Fn) {
    var num1 = 0
    var num2 = 0
    
    func plus(_ i: Int) -> (Int, Int) {
        num1 += i
        num2 += i << 1
        return (num1, num2)
    }
    
    func minus(_ i: Int) -> (Int, Int) {
        num1 -= i
        num2 -= i << 1
        return (num1, num2)
    }
    
    return (plus, minus)
}

let (p, m) = getFns()
p(5) // (5, 10)
m(4) // (1, 2)
p(3) // (4, 8)
m(2) // (2, 4)

等价的类实现:

swift 复制代码
class Closure {
    var num1 = 0
    var num2 = 0
    
    func plus(_ i: Int) -> (Int, Int) {
        num1 += i
        num2 += i << 1
        return (num1, num2)
    }
    
    func minus(_ i: Int) -> (Int, Int) {
        num1 -= i
        num2 -= i << 1
        return (num1, num2)
    }
}

var cs = Closure()
cs.plus(5)  // (5, 10)
cs.minus(4) // (1, 2)
cs.plus(3)  // (4, 8)
cs.minus(2) // (2, 4)

练习2:闭包数组

swift 复制代码
var functions: [() -> Int] = []

for i in 1...3 {
    functions.append { i }
}

for f in functions {
    print(f())
}
// 输出:
// 1
// 2
// 3

等价的类实现:

swift 复制代码
class Closure {
    var i: Int
    init(_ i: Int) {
        self.i = i
    }
    func get() -> Int {
        return i
    }
}

var clses: [Closure] = []
for i in 1...3 {
    clses.append(Closure(i))
}

for cls in clses {
    print(cls.get())
}

注意事项

如果返回值是函数类型,那么参数的修饰要保持统一。

swift 复制代码
func add(_ num: Int) -> (inout Int) -> Void {
    func plus(v: inout Int) {
        v += num
    }
    return plus
}

var num = 5
add(20)(&num)
print(num)  // 25

注意:

  • 返回的函数类型是(inout Int) -> Void
  • 内部函数plus的参数也必须是inout类型
  • 调用时需要使用&来传递inout参数

自动闭包

问题场景

swift 复制代码
// 如果第1个数大于0,返回第一个数。否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}

getFirstPositive(10, 20)  // 10
getFirstPositive(-2, 20)  // 20
getFirstPositive(0, -4)   // -4

使用函数类型参数

swift 复制代码
// 改成函数类型的参数,可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}

getFirstPositive(-4) { 20 }

使用@autoclosure

swift 复制代码
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}

getFirstPositive(-4, 20)

@autoclosure特点

  • @autoclosure会自动将20封装成闭包{ 20 }
  • @autoclosure只支持() -> T格式的参数
  • @autoclosure并非只支持最后1个参数
  • 空合并运算符??使用了@autoclosure技术
  • @autoclosure、无@autoclosure,构成了函数重载
  • 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行

实际应用示例

Swift中的空合并运算符??就是使用了@autoclosure

swift 复制代码
func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

这样可以避免不必要的计算:

swift 复制代码
let result = optionalValue ?? expensiveComputation()

只有当optionalValuenil时,expensiveComputation()才会被执行。


总结

闭包的核心概念

  1. 闭包表达式{ (参数) -> 返回值 in 函数体 }
  2. 简写形式:从完整形式到运算符,逐步简化
  3. 尾随闭包:提高代码可读性
  4. 闭包定义:函数 + 捕获的环境变量
  5. 内存模型:类似于类的实例对象

使用场景

  • 函数式编程
  • 异步回调
  • 数组操作(sort、map、filter等)
  • 延迟计算
  • 自定义控制流

最佳实践

  1. 能简写的地方尽量简写,提高代码简洁性
  2. 使用尾随闭包提高可读性
  3. 注意闭包的捕获机制,避免循环引用
  4. 合理使用@autoclosure进行延迟计算
  5. 理解闭包的内存模型,帮助优化性能
相关推荐
kymjs张涛2 小时前
零一开源|前沿技术周报 #7
android·前端·ios
思考着亮4 小时前
15-错误处理
ios
思考着亮4 小时前
9.方法
ios
思考着亮4 小时前
6.结构体和类
ios
咕噜签名分发冰淇淋7 小时前
申请注册苹果iOS企业级开发者证书需要公司拥有什么规模条件
macos·ios·cocoa
2501_9159184116 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview
不知名It水手18 小时前
uniapp运行项目到ios基座
ios·uni-app·cocoa
Digitally19 小时前
[5种方法] 如何将iPhone短信保存到电脑
ios·iphone
杂雾无尘1 天前
Swift 5.9 新特性揭秘:非复制类型的安全与高效
ios·swift·apple