Swift 闭包

Swift 闭包(Closures)全解析(2025 版)

“闭包是自包含的函数代码块” —— 掌握 闭包语法、捕获、@escaping、autoclosure、内存管理,写出 优雅、强大、函数式 的 Swift 代码!


一、什么是闭包?

闭包 = 匿名函数 + 捕获的上下文环境

{ (parameters) -> returnType in
    statements
}

三种形式

形式示例
全局函数func add(_ a: Int, _ b: Int) -> Int
嵌套函数func makeAdder() -> ((Int) -> Int)
闭包表达式{ $0 + $1 }

二、闭包基本语法

1. 完整写法

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

2. 类型推断(推荐)

let add = { (a, b) in a + b }

3. 简写参数 $0, $1

let multiply = { $0 * $1 }

4. 单表达式隐式返回

let square = { $0 * $0 }

5. 尾随闭包(Trailing Closure)

numbers.sorted { $0 < $1 }  // 推荐

三、闭包捕获(Capture)

闭包可以捕获外部变量,即使外部作用域已结束

func makeIncrementer(step: Int) -> () -> Int {
    var total = 0
    let incrementer = {
        total += step
        return total
    }
    return incrementer
}

let incBy5 = makeIncrementer(step: 5)
print(incBy5())  // 5
print(incBy5())  // 10

捕获的是引用,不是值!


四、闭包是引用类型

let closure1 = { print("A") }
let closure2 = closure1  // 复制引用
closure2()  // 打印 "A"

值捕获[weak self][unowned self] 防止循环引用


五、@escaping 闭包(逃逸闭包)

函数返回后,闭包仍被调用 → 必须标记 @escaping

class NetworkManager {
    var handlers: [() -> Void] = []

    func fetch(completion: @escaping () -> Void) {
        handlers.append(completion)
        DispatchQueue.global().async {
            completion()  // 逃逸!
        }
    }
}

何时加 @escaping

场景是否需要
立即调用不需要
存储到属性需要
异步回调需要
返回闭包需要

六、@autoclosure(自动闭包)

延迟执行,简化语法

func log(if condition: @autoclosure () -> Bool) {
    if condition() {
        print("条件为真")
    }
}

// 调用时不用写 {}
log(if 1 > 0)  // 自动包装成闭包

适用场景

  • assert
  • && 短路求值
  • 调试日志

七、闭包循环引用(Retain Cycle)

问题

class Person {
    var name = "小明"
    var onChange: (() -> Void)?

    func setup() {
        onChange = {
            print(self.name)  // 强引用 self
        }
    }

    deinit { print("Person deinit") }
}

PersononChange 互相强引用 → 内存泄漏

解决方案

onChange = { [weak self] in
    guard let self = self else { return }
    print(self.name)
}

// 或 unowned(不安全,self 必须存在)
onChange = { [unowned self] in
    print(self.name)
}

八、高阶函数 + 闭包实战

let numbers = [1, 6, 2, 8, 3, 9]

// map
let doubled = numbers.map { $0 * 2 }

// filter
let evens = numbers.filter { $0 % 2 == 0 }

// reduce
let sum = numbers.reduce(0) { $0 + $1 }

// sorted
let sorted = numbers.sorted { $0 < $1 }

// 链式
let result = numbers
    .filter { $0 > 5 }
    .map { "\($0 * 10)" }
    .joined(separator: ", ")
print(result)  // "60, 80, 90"

九、闭包性能优化

技巧说明
避免频繁创建缓存闭包
[weak self]防止循环引用
复杂逻辑抽取函数可读性 > 简写
inout 替代闭包性能敏感场景
// 缓存闭包
let expensiveClosure: () -> String = {
    // 复杂计算
    return "结果"
}()

十、闭包 vs 函数

特性闭包函数
匿名YesNo
捕获上下文YesNo
可内联YesYes
可赋值YesNo
尾随语法YesNo

十一、实战项目:事件总线(EventBus)

class EventBus {
    private var listeners: [String: [() -> Void]] = [:]

    func on(_ event: String, handler: @escaping () -> Void) {
        listeners[event, default: []].append(handler)
    }

    func emit(_ event: String) {
        listeners[event]?.forEach { $0() }
    }

    func off(_ event: String) {
        listeners[event] = nil
    }
}

// 使用
let bus = EventBus()

bus.on("login") { print("用户登录") }
bus.on("login") { print("记录日志") }

bus.emit("login")
// 输出:
// 用户登录
// 记录日志

十二、闭包速查表

语法示例
完整{ (a: Int) -> Int in return a * 2 }
推断{ a in a * 2 }
$0 简写{ $0 * 2 }
隐式返回{ $0 * 2 }
尾随闭包array.sorted { $0 < $1 }
捕获列表[weak self] in
@escaping@escaping () -> Void
@autoclosure@autoclosure () -> Bool

十三、练习题(当场写!)

// 1. 实现 once 函数:闭包只执行一次
func once(_ closure: @escaping () -> Void) -> () -> Void {
    // 你的代码
}

// 2. 实现 memoize:缓存函数结果
func memoize<T: Hashable, U>(_ f: @escaping (T) -> U) -> (T) -> U {
    // 你的代码
}

// 3. 实现链式加法:add(1)(2)(3)() == 6
func add(_ x: Int) -> (Int) -> Any {
    // 你的代码
}

答案(展开查看)

点击查看答案

// 1.
func once(_ closure: @escaping () -> Void) -> () -> Void {
    var executed = false
    return {
        guard !executed else { return }
        executed = true
        closure()
    }
}

// 使用
let sayHi = once { print("Hi") }
sayHi()  // Hi
sayHi()  // (无输出)

// 2.
func memoize<T: Hashable, U>(_ f: @escaping (T) -> U) -> (T) -> U {
    var cache: [T: U] = [:]
    return { arg in
        if let cached = cache[arg] {
            return cached
        }
        let result = f(arg)
        cache[arg] = result
        return result
    }
}

// 使用
let fib = memoize { (n: Int) -> Int in
    n <= 1 ? n : fib(n-1) + fib(n-2)
}

// 3.
func add(_ x: Int) -> (Int) -> Any {
    return { y in
        if y == 0 { return x }
        return add(x + y)
    }
}

// 使用
let result = add(1)(2)(3)(0) as! Int  // 6

总结:闭包黄金法则

法则说明
1. 优先用尾随闭包可读性高
2. 异步/存储闭包加 @escaping必须
3. 防止循环引用用 [weak self]防泄漏
4. 复杂闭包抽取为函数可维护
5. 用 @autoclosure 简化条件优雅
6. 性能敏感用 inout 替代闭包更快

你已完全掌握 Swift 闭包!


回复关键词继续学习

  • Swift 闭包内存管理
  • Swift 函数式编程进阶
  • Swift async/await 与闭包
  • SwiftUI @State & 闭包绑定

现在就实现一个 debounce 函数:输入频繁变化,只在停止 0.5 秒后执行!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部