Skip to content

Swift 入门(8):闭包

闭包(Closure)是 Swift 的核心特性之一,类似匿名函数。它能捕获上下文变量,非常常用。

基本语法

闭包的基本形式:

swift
{ (参数列表) -> 返回类型 in
    代码块
}

例如:

swift
let add = { (a: Int, b: Int) -> Int in
    return a + b
}
print(add(2, 3))   // 5

作为函数参数

swift
func operate(_ a: Int, _ b: Int, op: (Int, Int) -> Int) -> Int {
    return op(a, b)
}

let sum = operate(2, 3, op: { (x, y) in x + y })
print(sum)   // 5

闭包简写

Swift 提供了简写规则,使闭包更简洁:

  1. 省略参数类型和返回值
  2. 省略 return
  3. 使用参数占位符 $0, $1
swift
let nums = [3, 1, 5, 2]
let sorted = nums.sorted(by: { $0 < $1 })
print(sorted)   // [1, 2, 3, 5]

尾随闭包

当闭包是函数最后一个参数时,可以写在括号外:

swift
let doubled = nums.map { $0 * 2 }
print(doubled)   // [6, 2, 10, 4]

捕获变量

闭包会捕获外部变量:

swift
func makeCounter() -> () -> Int {
    var count = 0
    return {
        count += 1
        return count
    }
}

let counter = makeCounter()
print(counter())   // 1
print(counter())   // 2

常见应用场景

  1. 数组操作(map/filter/reduce)
  2. 回调(网络请求完成后执行)
  3. 延迟执行

实战:first(where:) 方法

基础用法

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

// 完整闭包语法
let firstEven = numbers.first(where: { (number) -> Bool in
    return number % 2 == 0
})
print(firstEven)  // Optional(8)

// 简化语法
let firstOdd = numbers.first { $0 % 2 == 1 }
print(firstOdd)   // Optional(1)

实际项目中的应用:寻找活跃窗口

swift
// 在 iOS 应用中寻找当前活跃的窗口
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
    return UIApplication.shared.windows.first {
        $0.isKeyWindow  // 尾随闭包:寻找 isKeyWindow 为 true 的窗口
    } ?? ASPresentationAnchor()
}

语法分解

swift
// 1. 完整写法
UIApplication.shared.windows.first(where: { window in
    return window.isKeyWindow
})

// 2. 省略参数类型和 return
UIApplication.shared.windows.first(where: { window in
    window.isKeyWindow
})

// 3. 使用参数占位符
UIApplication.shared.windows.first(where: { $0.isKeyWindow })

// 4. 尾随闭包语法(最简洁)
UIApplication.shared.windows.first { $0.isKeyWindow }

更多 first(where:) 实战示例

swift
// 寻找特定用户
let users = [
    User(name: "Alice", age: 25),
    User(name: "Bob", age: 30),
    User(name: "Charlie", age: 35)
]

let adult = users.first { $0.age >= 18 }
let alice = users.first { $0.name == "Alice" }

// 寻找特定视图控制器
let viewControllers = navigationController?.viewControllers ?? []
let targetVC = viewControllers.first { $0 is SettingsViewController }

// 寻找有效的网络配置
let configs = [config1, config2, config3]
let validConfig = configs.first { $0.isValid && $0.isEnabled }

与其他高阶函数的对比

swift
let data = [1, 2, 3, 4, 5]

// first(where:) - 找到第一个符合条件的元素
let firstEven = data.first { $0 % 2 == 0 }  // Optional(2)

// filter - 找到所有符合条件的元素
let allEvens = data.filter { $0 % 2 == 0 }  // [2, 4]

// contains(where:) - 判断是否存在符合条件的元素
let hasEven = data.contains { $0 % 2 == 0 }  // true

// firstIndex(where:) - 找到第一个符合条件的元素的索引
let index = data.firstIndex { $0 % 2 == 0 }  // Optional(1)

@escaping 闭包详解

什么是 @escaping?

swift
// 非逃逸闭包(默认)- 函数返回前必须执行
func processData(_ data: String, completion: (String) -> Void) {
    let result = data.uppercased()
    completion(result)  // 在函数返回前执行
}

// 逃逸闭包 - 可能在函数返回后执行
func processDataAsync(_ data: String, completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        let result = data.uppercased()
        completion(result)  // 在函数返回后执行
    }
}

实战:@objc 方法中的回调

swift
@objc public func register(_ optionsJson: String, _ cb: @escaping (String) -> Void) {
    // 这个回调会在异步操作完成后执行,所以需要 @escaping
}

语法分解

swift
// 1. @objc - 使方法可被 Objective-C 调用
// 2. public - 外部模块可访问
// 3. _ optionsJson: String - 无标签参数
// 4. _ cb: @escaping (String) -> Void - 逃逸闭包回调

@objc public func register(_ optionsJson: String, _ cb: @escaping (String) -> Void) {
    // 参数解析
    guard let options = parseOptions(optionsJson) else {
        cb("{\"ok\": false, \"errMsg\": \"Invalid options\"}")
        return
    }

    // 异步认证操作
    performAuthentication(options: options) { result in
        // 这个回调在异步操作完成后执行
        cb(result)  // 需要 @escaping 才能在这里调用
    }
}

为什么需要 @escaping?

swift
class AuthManager {
    private var callback: ((String) -> Void)?  // 存储回调

    // ❌ 编译错误:非逃逸闭包不能赋值给属性
    func badExample(_ completion: (String) -> Void) {
        self.callback = completion  // 错误!
    }

    // ✅ 正确:使用 @escaping
    func goodExample(_ completion: @escaping (String) -> Void) {
        self.callback = completion  // 正确!
    }

    func triggerCallback() {
        callback?("操作完成")
    }
}

实际应用场景

1. 网络请求回调

swift
@objc public func fetchUserData(_ userId: String, _ callback: @escaping (String) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        // 网络请求是异步的,回调在请求完成后执行
        if let data = data {
            callback(String(data: data, encoding: .utf8) ?? "")
        } else {
            callback("{\"error\": \"Network failed\"}")
        }
    }.resume()
}

2. 系统认证回调

swift
@objc public func authenticate(_ options: String, _ callback: @escaping (String) -> Void) {
    let authController = ASAuthorizationController(authorizationRequests: requests)

    // 存储回调,在认证完成后调用
    self.completionCallback = callback

    authController.delegate = self
    authController.performRequests()  // 异步操作
}

// 在协议方法中调用存储的回调
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
    completionCallback?("{\"ok\": true, \"data\": \"...\"}")
    completionCallback = nil
}

3. 延迟执行

swift
@objc public func delayedOperation(_ seconds: Double, _ callback: @escaping (String) -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
        callback("延迟操作完成")  // 在未来某个时间点执行
    }
}

闭包类型语法详解

swift
// (String) -> Void 的含义:
// ↓
// 接收一个 String 参数,无返回值的闭包类型

// 其他常见闭包类型
let closure1: () -> Void = { print("无参数无返回值") }
let closure2: (Int) -> String = { "\($0)" }
let closure3: (String, Int) -> Bool = { $0.count > $1 }

// 在函数参数中使用
func process(_ data: String, success: @escaping (String) -> Void, failure: @escaping (Error) -> Void) {
    // 成功和失败的不同回调
}

@escaping vs 非逃逸对比

特性非逃逸闭包(默认)@escaping 闭包
执行时机函数返回前必须执行可在函数返回后执行
存储不能赋值给属性可以赋值给属性
异步操作不支持支持
性能更好(编译器优化)稍差(需要内存管理)
使用场景同步操作、集合遍历异步操作、回调存储

实际使用建议

swift
// ✅ 同步操作用非逃逸闭包
func transform<T>(_ items: [T], _ transform: (T) -> T) -> [T] {
    return items.map(transform)  // 立即执行
}

// ✅ 异步操作用 @escaping
func loadData(_ completion: @escaping (Data?) -> Void) {
    DispatchQueue.global().async {
        // 异步加载数据
        completion(data)
    }
}

// ✅ 需要存储回调用 @escaping
class APIClient {
    private var onComplete: ((String) -> Void)?

    func request(_ callback: @escaping (String) -> Void) {
        self.onComplete = callback  // 存储以备后用
    }
}

小结

  • 闭包 = 匿名函数,可以捕获外部变量。
  • 语法简洁,支持尾随闭包。
  • @escaping 用于可能在函数返回后执行的闭包。
  • 异步操作、回调存储都需要 @escaping
  • 常用于集合操作、网络请求和系统 API 回调。

本站总访问