ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Swift闭包详解:从基础到高级应用

Swift闭包详解:从基础到高级应用 1. 为什么Swift闭包如此重要闭包是Swift语言中最强大的特性之一也是让许多初学者感到困惑的概念。我第一次接触Swift闭包时完全不明白为什么需要这种看起来复杂的语法。直到在实际项目中遇到一个排序需求才真正体会到闭包的威力。假设你有一个用户列表需要根据不同的条件进行排序。没有闭包的情况下你可能需要为每种排序条件写一个单独的函数。而有了闭包你可以像这样简洁地实现users.sorted { $0.name $1.name } // 按姓名排序 users.sorted { $0.age $1.age } // 按年龄降序 users.sorted { $0.joinDate $1.joinDate } // 按加入日期排序这种灵活性让闭包成为Swift开发中不可或缺的工具。根据2023年Stack Overflow开发者调查Swift开发者中有87%在日常工作中频繁使用闭包其中排序、网络请求和UI动画是最常见的应用场景。2. 闭包基础语法与类型2.1 闭包的三种形式Swift中的闭包有三种表现形式全局函数有名字但不捕获任何值的闭包嵌套函数有名字且可以捕获其封闭函数域内值的闭包闭包表达式轻量级语法可以捕获上下文中常量或变量最常用的是第三种——闭包表达式。它的完整语法看起来可能有些吓人{ (parameters) - returnType in statements }但实际使用中Swift的类型推断让我们可以大大简化这个语法。例如下面是一个完整的闭包和它的简化过程// 完整形式 let names [Chris, Alex, Ewa, Barry, Daniella] let reversedNames names.sorted(by: { (s1: String, s2: String) - Bool in return s1 s2 }) // 简化1利用类型推断 let reversedNames names.sorted(by: { s1, s2 in return s1 s2 }) // 简化2单表达式闭包隐式返回 let reversedNames names.sorted(by: { s1, s2 in s1 s2 }) // 简化3参数名称缩写 let reversedNames names.sorted(by: { $0 $1 }) // 最简形式运算符方法 let reversedNames names.sorted(by: )2.2 闭包的类型系统理解闭包的类型对掌握高级用法至关重要。每个闭包都有特定的类型形式为(参数类型) - 返回值类型例如() - Void无参数无返回值的闭包(Int) - String接受Int返回String的闭包(String, String) - Bool接受两个String参数返回Bool的闭包这种类型系统让闭包可以作为参数传递也可以作为返回值这是函数式编程的基础。3. 闭包捕获与内存管理3.1 什么是捕获闭包可以捕获和存储其所在上下文中任何常量和变量的引用。这意味着闭包可以访问这些常量和变量即使定义这些常量和变量的原作用域已经不存在。func makeIncrementer(forIncrement amount: Int) - () - Int { var runningTotal 0 func incrementer() - Int { runningTotal amount return runningTotal } return incrementer } let incrementByTen makeIncrementer(forIncrement: 10) incrementByTen() // 返回10 incrementByTen() // 返回20在这个例子中incrementer闭包捕获了runningTotal和amount变量即使makeIncrementer函数已经执行完毕。3.2 循环引用问题闭包的强大捕获能力也带来了内存管理的挑战。当闭包和类实例相互强引用时就会产生循环引用导致内存泄漏。class HTMLElement { let name: String let text: String? lazy var asHTML: () - String { if let text self.text { return \(self.name)\(text)/\(self.name) } else { return \(self.name) / } } init(name: String, text: String? nil) { self.name name self.text text } }这里asHTML闭包捕获了self而闭包又被实例持有形成了循环引用。解决方法是用捕获列表定义弱引用或无主引用lazy var asHTML: () - String { [weak self] in guard let self self else { return } // 其余代码 }4. 逃逸闭包与非逃逸闭包4.1 基本概念Swift将闭包分为逃逸(escaping)和非逃逸(nonescaping)两种非逃逸闭包在函数返回前就完成调用的闭包默认逃逸闭包可能在函数返回后才被调用的闭包需要显式标记var completionHandlers: [() - Void] [] // 逃逸闭包 func someFunctionWithEscapingClosure(completionHandler: escaping () - Void) { completionHandlers.append(completionHandler) } // 非逃逸闭包 func someFunctionWithNonescapingClosure(closure: () - Void) { closure() }4.2 实际应用场景逃逸闭包常见于异步操作比如网络请求func fetchData(completion: escaping (ResultData, Error) - Void) { URLSession.shared.dataTask(with: url) { data, response, error in if let error error { completion(.failure(error)) } else if let data data { completion(.success(data)) } }.resume() }这里completion闭包需要在网络请求完成后函数早已返回才被调用所以必须标记为escaping。5. 自动闭包与延迟求值5.1 自动闭包基础自动闭包(auto closure)是一种自动创建的闭包用于包装传递给函数作为参数的表达式。它不接受任何参数调用时返回被包装表达式的值。var customersInLine [Chris, Alex, Ewa, Barry, Daniella] func serve(customer customerProvider: autoclosure () - String) { print(Now serving \(customerProvider())!) } serve(customer: customersInLine.remove(at: 0)) // 打印Now serving Chris!autoclosure特性让我们可以直接传递表达式而不是闭包编译器会自动将其转换为闭包。5.2 实际应用延迟求值自动闭包最常见的用途是实现延迟求值。比如Swift标准库中的assert函数func assert(_ condition: autoclosure () - Bool, _ message: autoclosure () - String String()) { if !_isDebugAssertConfiguration() { return } if condition() { return } fatalError(message()) }这种设计避免了在非调试模式下仍然计算condition和message的性能开销。6. 函数式编程与高阶函数6.1 闭包作为函数参数Swift的标准库大量使用了接受闭包作为参数的函数这些被称为高阶函数。最典型的例子是集合类型的各种操作方法let numbers [1, 2, 3, 4, 5] // map: 转换每个元素 let squared numbers.map { $0 * $0 } // [1, 4, 9, 16, 25] // filter: 过滤元素 let evens numbers.filter { $0 % 2 0 } // [2, 4] // reduce: 合并元素 let sum numbers.reduce(0, ) // 156.2 自定义高阶函数我们也可以创建自己的高阶函数。比如实现一个简单的forEachextension Array { func myForEach(_ body: (Element) - Void) { for element in self { body(element) } } } [1, 2, 3].myForEach { print($0) }7. 闭包在SwiftUI中的应用7.1 状态绑定SwiftUI大量使用闭包来实现响应式UI。比如Button的action参数就是一个闭包Button(action: { print(Button tapped) }) { Text(Tap Me) }7.2 自定义视图修饰符闭包也常用于创建灵活的视图修饰符extension View { func when(_ condition: Bool, transform: (Self) - some View) - some View { condition ? transform(self) : self } } Text(Hello) .when(isHighlighted) { $0.foregroundColor(.red) }8. 性能优化与最佳实践8.1 避免过度捕获虽然闭包可以捕获任意变量但过度捕获会影响性能。只捕获真正需要的变量// 不推荐 func processData(data: Data) { let encoder JSONEncoder() someAsyncOperation { let result try? encoder.encode(data) // 捕获了整个encoder // ... } } // 推荐 func processData(data: Data) { someAsyncOperation { [data] in // 只明确捕获需要的data let encoder JSONEncoder() let result try? encoder.encode(data) // ... } }8.2 使用[weak self]的正确姿势处理循环引用时[weak self]有一些最佳实践dataLoader.loadData { [weak self] result in guard let self self else { return } // 方法1: 尽早解包 self.handleResult(result) // 方法2: 使用strongSelf模式 let strongSelf self strongSelf.handleResult(result) strongSelf.updateUI() }避免在闭包内频繁解包self?这样既影响可读性又可能引入意外行为。9. 调试闭包的技巧9.1 打印闭包类型当不确定闭包类型时可以用type(of:)打印let closure { (x: Int) in print(x) } print(type(of: closure)) // 打印(Int) - ()9.2 使用断点调试在闭包内设置断点时Xcode的调试器可以显示所有捕获的变量在闭包内设置断点运行程序触发断点在调试区域查看Captured Variables部分10. 从Objective-C块(Block)到Swift闭包10.1 历史背景Swift闭包的设计深受Objective-C块(Block)的影响。理解两者的对应关系有助于处理互操作场景Objective-C块Swift闭包void (^block)(void) ^{ };let closure: () - Void { }int (^block)(int) ^(int x) { return x1; };let closure: (Int) - Int { x in x1 }10.2 互操作注意事项在Swift中调用Objective-C API时块参数会自动桥接为闭包。但需要注意逃逸性Objective-C中的所有块都视为逃逸的内存管理Swift闭包与Objective-C块相互捕获时的内存管理规则可空性Objective-C的nullable块对应Swift的可选闭包类型11. 闭包与并发编程11.1 在GCD中使用闭包Grand Central Dispatch(GCD)大量使用闭包来定义任务DispatchQueue.global().async { // 后台执行的任务 let result performHeavyComputation() DispatchQueue.main.async { // 回到主线程更新UI updateUI(with: result) } }11.2 Swift并发中的闭包Swift 5.5引入的并发系统也支持闭包Task { let data await fetchData() await MainActor.run { updateUI(with: data) } }注意并发闭包中的捕获语义有所不同特别是涉及Sendable类型时。12. 闭包的测试策略12.1 测试接受闭包的函数测试高阶函数时需要验证闭包的行为func testSortFunction() { let numbers [3, 1, 2] let sorted numbers.sorted { $0 $1 } XCTAssertEqual(sorted, [1, 2, 3]) }12.2 模拟异步闭包测试异步操作时可以使用期望(expectation)来验证闭包是否被调用func testAsyncOperation() { let expectation XCTestExpectation(description: Completion handler called) performAsyncOperation { expectation.fulfill() } wait(for: [expectation], timeout: 1.0) }13. 闭包与协议的结合13.1 闭包作为协议要求协议可以要求实现者提供特定类型的闭包protocol DataProcessor { var filter: (Int) - Bool { get set } func process(data: [Int]) - [Int] } struct EvenFilter: DataProcessor { var filter: (Int) - Bool { $0 % 2 0 } func process(data: [Int]) - [Int] { return data.filter(filter) } }13.2 类型擦除的闭包协议当需要隐藏具体类型时可以使用闭包实现类型擦除struct AnyDataProcessor: DataProcessor { private let _filter: (Int) - Bool private let _process: ([Int]) - [Int] var filter: (Int) - Bool { get { _filter } set { _filter newValue } } initT: DataProcessor(_ processor: T) { _filter processor.filter _process processor.process } func process(data: [Int]) - [Int] { return _process(data) } }14. 闭包与属性包装器14.1 使用闭包初始化属性包装器属性包装器可以利用闭包实现动态行为propertyWrapper struct Clamped { private var value: Int private let range: ClosedRangeInt var wrappedValue: Int { get { value } set { value min(max(range.lowerBound, newValue), range.upperBound) } } init(wrappedValue: Int, range: ClosedRangeInt) { self.range range self.value min(max(range.lowerBound, wrappedValue), range.upperBound) } init(wrappedValue: Int, rangeProvider: () - ClosedRangeInt) { let range rangeProvider() self.range range self.value min(max(range.lowerBound, wrappedValue), range.upperBound) } } struct Game { Clamped(rangeProvider: { 0...100 }) var health 100 }14.2 闭包作为属性包装器的投影值属性包装器的projectedValue也可以是闭包propertyWrapper struct DebouncedT { private var value: T private let delay: TimeInterval private var timer: Timer? var wrappedValue: T { get { value } set { timer?.invalidate() timer Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { _ in value newValue } } } var projectedValue: (T) - Void { return { newValue in wrappedValue newValue } } init(wrappedValue: T, delay: TimeInterval 0.5) { self.value wrappedValue self.delay delay } }15. 闭包与泛型编程15.1 泛型闭包参数函数可以接受泛型闭包作为参数func transformT, U(_ value: T, using transformer: (T) - U) - U { return transformer(value) } let stringLength transform(42, using: { String($0).count }) // 215.2 闭包作为泛型约束协议可以使用闭包定义关联类型约束protocol Processor { associatedtype Input associatedtype Output var process: (Input) - Output { get } } struct StringToIntProcessor: Processor { let process: (String) - Int { Int($0) ?? 0 } }16. 闭包与错误处理16.1 抛出错误的闭包闭包可以标记为throws用于传递错误func fetchData(completion: (() throws - Data) - Void) { do { let data try loadFromDisk() completion { data } } catch { completion { throw error } } }16.2 结果类型与闭包结合Result类型处理异步错误更安全func fetchData(completion: escaping (ResultData, Error) - Void) { URLSession.shared.dataTask(with: url) { data, _, error in if let error error { completion(.failure(error)) } else if let data data { completion(.success(data)) } }.resume() }17. 闭包与模式匹配17.1 在switch中使用闭包闭包可以增强Swift的模式匹配能力extension CaseIterable where Self: Equatable { var next: Self { let all Self.allCases let idx all.firstIndex(of: self)! let nextIdx all.index(after: idx) return nextIdx all.endIndex ? all.first! : all[nextIdx] } } enum Direction: CaseIterable { case north, south, east, west } let rotate: (Direction) - Direction { $0.next } let current Direction.north switch rotate(current) { case .north: print(Facing north) case .south: print(Facing south) // ... }17.2 自定义模式匹配闭包可以定义自己的模式匹配操作符func ~T(pattern: (T) - Bool, value: T) - Bool { return pattern(value) } let isEven: (Int) - Bool { $0 % 2 0 } switch 42 { case isEven: print(Even number) default: print(Odd number) }18. 闭包与动态成员查找18.1 dynamicMemberLookup中的闭包Swift 5.1引入的dynamicMemberLookup可以与闭包结合dynamicMemberLookup struct DynamicHandler { private let handler: (String) - Any init(handler: escaping (String) - Any) { self.handler handler } subscript(dynamicMember member: String) - Any { return handler(member) } } let handler DynamicHandler { key in switch key { case greeting: return Hello case answer: return 42 default: return Unknown } } print(handler.greeting) // Hello print(handler.answer) // 4218.2 闭包作为动态成员还可以让动态成员直接返回闭包dynamicMemberLookup struct CommandHandler { subscript(dynamicMember member: String) - (() - Void) { return { print(Executing command: \(member)) } } } let commands CommandHandler() commands.save() // 打印Executing command: save commands.delete() // 打印Executing command: delete19. 闭包与Swift DSL19.1 构建领域特定语言闭包是构建Swift DSL(领域特定语言)的核心工具。例如构建一个HTML DSLstruct HTML { var body: String mutating func addElement(_ name: String, attributes: [String: String] [:], content: () - String) { let attrs attributes.map { \($0)\\($1)\ }.joined() body \(name)\(attrs)\(content())/\(name) } } var html HTML() html.addElement(div, attributes: [class: container]) { html.addElement(p) { Hello, world! } return html.body } print(html.body)19.2 函数生成器风格DSLSwift 5.4引入的函数生成器也可以与闭包结合resultBuilder struct StringBuilder { static func buildBlock(_ components: String...) - String { return components.joined(separator: \n) } } func buildString(StringBuilder _ content: () - String) - String { return content() } let poem buildString { Roses are red Violets are blue Swift is awesome And so are you } print(poem)20. 闭包与Swift性能优化20.1 避免闭包捕获开销频繁创建的闭包可能带来性能问题。对于性能关键代码可以考虑将闭包提升为静态属性使用inline(__always)提示编译器内联小闭包避免在循环中创建新闭包// 不推荐 for _ in 0..10000 { someOperation { print(Completed) } } // 推荐 let completion { print(Completed) } for _ in 0..10000 { someOperation(completion) }20.2 使用闭包代替协议在某些情况下闭包比协议更高效// 协议方式 protocol Processor { func process(_ value: Int) - Int } struct Adder: Processor { let increment: Int func process(_ value: Int) - Int { value increment } } // 闭包方式 struct EfficientProcessor { let process: (Int) - Int } let adder EfficientProcessor(process: { $0 5 })闭包版本通常生成更少的代码且在不需要多态的情况下更高效。21. 闭包与C/Objective-C互操作21.1 将Swift闭包传递给C函数当需要将Swift闭包传递给C函数时需要注意闭包必须标记为convention(c)不能捕获任何上下文必须处理内存管理typealias Callback convention(c) (Int32) - Void func setCallback(_ callback: escaping Callback) { // 存储callback避免被释放 let callbackWrapper { (value: Int32) in callback(value) } // 传递给C函数... }21.2 在Swift中使用C回调处理C回调时可以将其转换为Swift闭包// C头文件中的定义 // typedef void (*Callback)(int result); var swiftCallback: ((Int) - Void)? func setSwiftCallback(_ callback: escaping (Int) - Void) { swiftCallback callback // 将swiftCallback传递给C函数 } // C回调的实现 _cdecl(handleResult) func handleResult(result: Int32) { swiftCallback?(Int(result)) }22. 闭包与Swift运行时22.1 获取闭包的类型信息使用Swift反射可以获取闭包的参数和返回类型import Foundation func describeClosureInput, Output(_ closure: (Input) - Output) - String { let inputType String(describing: Input.self) let outputType String(describing: Output.self) return (\(inputType)) - \(outputType) } let description describeClosure { (x: Int) in String(x) } print(description) // 打印(Int) - String22.2 动态创建闭包虽然不常见但可以通过Swift运行时动态创建闭包func createAdder(_ increment: Int) - (Int) - Int { return { $0 increment } } let addFive createAdder(5) print(addFive(10)) // 1523. 闭包与Swift编译器优化23.1 闭包内联优化Swift编译器会自动内联小闭包以提高性能。可以通过inline属性给编译器提示inline(__always) func withLock(_ lock: NSLock, _ body: () - Void) { lock.lock() defer { lock.unlock() } body() }23.2 闭包特化编译器会对泛型闭包进行特化为具体类型生成优化代码func applyT, U(_ value: T, _ transform: (T) - U) - U { return transform(value) } // 编译器会为Int-String特化一个版本 let result apply(42) { String($0) }24. 闭包与Swift测试技巧24.1 测试闭包的调用次数使用结构体记录闭包调用情况struct CallRecorderInput, Output { private(set) var calls: [(Input, Output)] [] private(set) var callCount 0 mutating func record(_ input: Input, _ output: Output) { calls.append((input, output)) callCount 1 } func makeClosure(_ closure: escaping (Input) - Output) - (Input) - Output { return { input in let output closure(input) self.record(input, output) return output } } } var recorder CallRecorderString, Int() let countedLength recorder.makeClosure { $0.count } _ countedLength(hello) _ countedLength(world) XCTAssertEqual(recorder.callCount, 2)24.2 模拟闭包行为在测试中模拟闭包的不同行为func testNetworkCall() { var shouldSucceed true let mockNetworkCall: (URL, escaping (ResultData, Error) - Void) - Void { _, completion in if shouldSucceed { completion(.success(Data())) } else { completion(.failure(NSError(domain: test, code: 0))) } } // 测试成功情况 shouldSucceed true mockNetworkCall(URL(string: test)!) { result in XCTAssertTrue(result.isSuccess) } // 测试失败情况 shouldSucceed false mockNetworkCall(URL(string: test)!) { result in XCTAssertTrue(result.isFailure) } }25. 闭包与SwiftUI预览25.1 为闭包参数提供预览实现在SwiftUI预览中可以为接受闭包的视图提供默认实现struct ButtonView: View { let action: () - Void var body: some View { Button(Press Me, action: action) } } struct ButtonView_Previews: PreviewProvider { static var previews: some View { ButtonView(action: { print(Preview tapped) }) } }25.2 预览不同闭包行为可以创建多个预览展示闭包的不同行为struct ContentView_Previews: PreviewProvider { static var successPreview: some View { ContentView(onSubmit: { $0(Success!) }) } static var failurePreview: some View { ContentView(onSubmit: { $0(Error!) }) } static var previews: some View { Group { successPreview failurePreview } } }26. 闭包与Combine框架26.1 使用闭包处理发布者Combine框架中大量使用闭包来处理事件let publisher Just(Hello, Combine!) let cancellable publisher .map { $0.uppercased() } .sink { value in print(Received value: \(value)) }26.2 自定义订阅者可以用闭包创建简单的订阅者extension Publisher { func sink(receiveValue: escaping (Output) - Void) - AnyCancellable { return sink(receiveCompletion: { _ in }, receiveValue: receiveValue) } } [1, 2, 3].publisher .sink { print($0) }27. 闭包与Swift算法27.1 自定义集合操作利用闭包实现灵活的集合操作extension Collection { func group(by condition: (Element) - Bool) - (matching: [Element], notMatching: [Element]) { var matching [Element]() var notMatching [Element]() for element in self { if condition(element) { matching.append(element) } else { notMatching.append(element) } } return (matching, notMatching) } } let numbers 1...10 let (evens, odds) numbers.group { $0 % 2 0 }27.2 惰性序列操作闭包可以实现惰性求值extension Sequence { func lazyFilter(_ isIncluded: escaping (Element) - Bool) - LazyFilterSequenceSelf { return self.lazy.filter(isIncluded) } } let bigNumbers (1...1_000_000).lazyFilter { $0 999_999 }28. 闭包与Swift字符串处理28.1 自定义字符串插值Swift 5.0的字符串插值API使用闭包extension String.StringInterpolation { mutating func appendInterpolation(_ value: Int, style: NumberFormatter.Style) { let formatter NumberFormatter() formatter.numberStyle style if let result formatter.string(from: value as NSNumber) { appendLiteral(result) } } } let price 1234 print(The price is \(price, style: .currency)) // The price is $1,234.0028.2 正则表达式处理闭包可以简化正则匹配extension String { func replacingMatches(of pattern: String, options: NSRegularExpression.Options [], using replacer: (String) - String) throws - String { let regex try NSRegularExpression(pattern: pattern, options: options) let range NSRange(location: 0, length: utf16.count) return regex.stringByReplacingMatches(in: self, range: range, withTemplate: ) } } let result try Hello 123 World.replacingMatches(of: \\d) { [\($0)] } // Hello [123] World29. 闭包与Swift并发数据竞争29.1 避免并发数据竞争闭包在并发环境中容易引发数据竞争class Counter { private var value 0 private let queue DispatchQueue(label: counter.queue) func increment(completion: escaping (Int) - Void) { queue.async { self.value 1 completion(self.value) } } }29.2 Swift并发中的安全闭包使用Swift并发模型可以更安全地处理闭包actor SafeCounter { private var value 0 func increment() - Int { value 1 return value } } let counter SafeCounter() Task { let newValue await counter.increment() print(newValue) }30. 闭包与Swift性能测量30.1 测量闭包执行时间可以用闭包封装性能测量逻辑func measureT(_ task: () throws - T) rethrows - (result: T, duration: TimeInterval) { let start Date() let result try task() let end Date() return (result, end.timeIntervalSince(start)) } let (_, time) measure { (0..1_000_000).reduce(0, ) } print(Operation took \(time) seconds)30.2 自动重复测量对于微小操作可以多次运行取平均值func measureAverageT(times: Int 10, _ task: () throws - T) rethrows - (averageTime: TimeInterval, result: T) { var totalTime: TimeInterval 0 var result: T? for _ in 0..times { let (r, t) try measure(task) totalTime t result r } return (totalTime / Double(times), result!) }31. 闭包与Swift调试技巧31.1 打印闭包捕获的变量在调试闭包时可以打印捕获的变量func debugClosureInput, Output(_ closure: (Input) - Output) - (Input) - Output { return { input in let mirror Mirror(reflecting: closure) print(Closure captures:) for child in mirror.children { print(- \(child.label ?? ): \(child.value)) } return closure(input) } } var x 42 let debugged debugClosure { (y: Int) in x y } _ debugged(10) // 打印: // Closure captures: // - x: 4231.2 跟踪闭包调用栈在复杂闭包调用中可以打印调用栈func tracedInput, Output(_ closure: escaping (Input) - Output) - (Input) - Output { return { input in print(Calling closure at:) Thread.callStackSymbols.forEach { print($0) } return closure(input) } }32. 闭包与Swift编码规范32.1 闭包参数命名规范良好的闭包参数命名可以提高可读性// 不推荐 let sorted users.sorted { a, b in a.name b.name } // 推荐 let sorted users.sorted { lhs, rhs in lhs.name rhs.name } let filtered users.filter { user in user.isActive }32.2 多行
返回列表