Appearance
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 提供了简写规则,使闭包更简洁:
- 省略参数类型和返回值
- 省略 return
- 使用参数占位符
$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常见应用场景
- 数组操作(map/filter/reduce)
- 回调(网络请求完成后执行)
- 延迟执行
实战: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 回调。