聊聊swift闭包
Swift闭包语法详解
代码示例
var b:() -> Void = {() -> Void in
print("匿名函数")
}
b()
详细解释
这是一个Swift中的闭包(Closure)语法,具体解释如下:
代码分解
var b:() -> Void = {() -> Void in
print("匿名函数")
}
b()
语法分析
-
变量声明:
-
var b:声明一个变量b -
:() -> Void:指定变量的类型是一个函数类型,() -> Void表示:-
():不接受任何参数 -
-> Void:返回Void类型(即不返回值,相当于空类型)
-
-
-
闭包赋值:
-
= {() -> Void in:赋值给一个闭包表达式-
{}:闭包的开始和结束 -
() -> Void:闭包的类型签名(与变量类型匹配) -
in:关键字,表示参数列表和函数体的分隔符
-
-
-
闭包体:
-
print("匿名函数"):闭包执行的代码
-
-
调用:
-
b():调用这个闭包函数
-
等价写法
这个闭包还可以写成更简洁的形式:
完整写法(上面使用的)
var b: () -> Void = { () -> Void in
print("匿名函数")
}
简化写法(省略类型,因为可以从上下文推断)
var b: () -> Void = {
print("匿名函数")
}
尾随闭包写法(如果作为参数传递)
func executeClosure(closure: () -> Void) {
closure()
}
executeClosure {
print("匿名函数")
}
关键概念
- 闭包:可以捕获和存储其所在上下文中变量和常量的引用
- 匿名函数:没有名字的函数,可以直接赋值给变量或作为参数传递
- 类型推断:Swift可以根据上下文自动推断闭包的类型
执行结果
执行这段代码会输出:匿名函数
闭包的特点
- 可以捕获上下文:闭包可以捕获和存储其所在作用域中的变量和常量
- 可以作为参数传递:可以作为函数的参数传递给其他函数
- 可以作为返回值:函数可以返回闭包作为返回值
-
有三种形式:
- 全局函数:有名字但不能捕获值
- 嵌套函数:有名字且能捕获其封闭函数域内的值
- 闭包表达式:无名字的轻量级语法,能捕获其上下文中变量或常量的值
命名函数 vs 闭包表达式的区别
示例代码对比
第一种写法:命名函数赋值
func test3(param:[Int]) -> String
{
var temp:String = ""
for item in param {
temp = temp + String(item)
}
return temp
}
var e:([Int]) -> String = test3
print(e([2,3])) // 输出:23
第二种写法:闭包表达式
var f:([Int]) -> String = {
(a:[Int]) -> String in
var temp:String = ""
for item in a {
temp = temp + String(item)
}
return temp
}
print(f([4,5,6])) // 输出:456
核心区别
1. 定义方式
-
命名函数:使用
func关键字定义,有函数名,可以在其他地方重复调用 - 闭包表达式:直接在赋值时定义,没有函数名,是一次性使用的匿名函数
2. 作用域和生命周期
- 命名函数:有独立的生命周期,可以在定义后多次调用
- 闭包表达式:赋值给变量后,通过变量名调用,变量的生命周期决定了闭包的生命周期
3. 内存占用
- 命名函数:函数本身占用内存,但赋值给变量时只是引用传递
- 闭包表达式:闭包本身和捕获的上下文都会占用内存
4. 使用场景
- 命名函数:适用于需要重用的逻辑,函数体复杂,功能独立的情况
- 闭包表达式:适用于简单的回调逻辑,作为参数传递,或需要捕获上下文的场景
5. 语法特点
- 命名函数:有独立的函数签名,可以有默认参数等
- 闭包表达式:语法更简洁,可以省略类型标注(类型推断)
实际应用场景
闭包在Swift开发中非常常用,比如:
- 异步回调
- 集合类型的操作(如map、filter、sort)
- UI事件处理
- 定时器回调
- 网络请求回调
性能考虑
- 命名函数:通常性能更好,因为没有额外的上下文捕获开销
- 闭包表达式:如果捕获了大量上下文变量,可能产生循环引用或内存泄漏风险
最佳实践
-
使用命名函数当:
- 函数逻辑复杂且需要重用
- 函数需要有明确的名称便于调试
- 不需要捕获外部上下文
-
使用闭包表达式当:
- 需要简单的回调逻辑
- 作为参数传递给其他函数
- 需要捕获和修改外部变量
- 用于内联定义,提高代码可读性
闭包作为函数参数的进阶用法
示例代码分析
1. 无参数闭包
func test(param:() -> Void)
{
param()
}
// 调用方式:尾随闭包语法
test{
print("test")
}
分析:
-
test函数接受一个() -> Void类型的闭包参数 - 使用尾随闭包语法调用:
test{ print("test") } - 省略了
param:标签,直接在大括号中定义闭包
2. 单参数闭包
func test2(param:(Int)-> Void)
{
param(10)
}
// 完整写法
test2(param: {(value:Int) -> Void in
print(value)
})
// 简化写法(尾随闭包)
test2{ (value) in
print(value)
}
分析:
-
test2函数接受(Int) -> Void类型的闭包 - 完整写法:明确指定参数类型和返回类型
- 简化写法:省略参数类型(类型推断),使用尾随闭包语法
3. 多参数有返回值闭包
func test3(param:(Int, Int) -> Int)
{
print(param(10,30))
}
// 完整写法
test3(param:{(item1, item2) -> Int in
return item1 + item2
})
// 极简写法
test3(param:{
$0 + $1 // 使用$0、$1表示第一个和第二个参数
})
分析:
-
test3函数接受(Int, Int) -> Int类型的闭包 - 完整写法:明确参数名和返回类型
- 极简写法:使用
$0、$1等简写参数名,省略return和in
记忆要点
闭包语法简化规则:
- 参数类型省略:如果能从上下文推断,可以省略参数类型
-
返回类型省略:单表达式闭包可以省略
return -
参数名简化:可以使用
$0、$1等代替参数名 - 圆括号省略:单参数时可以省略参数圆括号
尾随闭包语法规则:
- 当闭包是函数的最后一个参数时,可以使用尾随闭包语法
- 省略参数标签,直接在大括号中定义闭包
- 使代码更简洁易读
实际运行结果
// test{ print("test") } 输出:test
// test2{ (value) in print(value) } 输出:10
// test3{ $0 + $1 } 传入(10,30) 输出:40
常见应用场景详解
1. 网络请求异步回调
// 模拟网络请求函数
func fetchUserData(userId: String, completion: @escaping (User?, Error?) -> Void) {
DispatchQueue.global().async {
// 模拟网络延迟
Thread.sleep(forTimeInterval: 1.0)
// 模拟成功获取用户数据
let user = User(name: "张三", age: 25)
DispatchQueue.main.async {
completion(user, nil)
}
}
}
// 使用示例
fetchUserData(userId: "123") { user, error in
if let user = user {
print("获取用户成功:\(user.name), 年龄:\(user.age)")
// 在主线程更新UI
self.updateUI(with: user)
} else if let error = error {
print("获取用户失败:\(error.localizedDescription)")
}
}
实际应用:iOS开发中所有网络请求几乎都使用闭包回调,如URLSession、Alamofire等。
2. 数组和集合操作
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// map:转换每个元素
let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// filter:过滤元素
let evenNumbers = numbers.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]
// reduce:累积计算
let sum = numbers.reduce(0) { $0 + $1 } // 55
// sort:排序
let sorted = numbers.sorted { $0 > $1 } // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// 链式调用
let result = numbers
.filter { $0 > 5 }
.map { $0 * $0 }
.reduce(0, +) // 平方和:36+49+64+81+100=330
实际应用:处理数据转换、过滤、统计等操作。
3. UI事件处理
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// UIButton点击事件
let button = UIButton(type: .system)
button.setTitle("点击我", for: .normal)
button.addAction(UIAction { [weak self] _ in
self?.handleButtonTap()
}, for: .touchUpInside)
// UITextField文本变化监听
let textField = UITextField()
textField.addAction(UIAction { _ in
print("文本变化:\(textField.text ?? "")")
}, for: .editingChanged)
// 定时器
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print("定时器触发")
}
}
func handleButtonTap() {
print("按钮被点击了!")
// 处理点击逻辑
}
}
实际应用:所有UI控件的事件响应都使用闭包。
4. 动画和过渡效果
import UIKit
class AnimationViewController: UIViewController {
let animatedView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// UIView动画
UIView.animate(withDuration: 0.5) {
self.animatedView.alpha = 0.5
self.animatedView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
} completion: { finished in
if finished {
print("动画完成")
// 执行后续操作
}
}
// UIViewPropertyAnimator
let animator = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut) {
self.animatedView.center = CGPoint(x: 200, y: 300)
}
animator.addCompletion { position in
switch position {
case .end:
print("动画正常结束")
case .current:
print("动画被中断")
case .start:
print("动画开始")
@unknown default:
break
}
}
animator.startAnimation()
}
}
实际应用:所有iOS动画效果都依赖闭包。
5. 多线程和并发
import Foundation
class DataManager {
// 异步数据处理
func processDataAsync(data: [Int], completion: @escaping ([Int]) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
// 耗时操作
let processedData = data.map { $0 * $0 }
// 回到主线程回调
DispatchQueue.main.async {
completion(processedData)
}
}
}
// 并行处理
func processInParallel(data: [Int], completion: @escaping ([Int]) -> Void) {
let group = DispatchGroup()
var results = [Int]()
let lock = DispatchQueue(label: "com.example.lock")
for item in data {
group.enter()
DispatchQueue.global().async {
// 模拟耗时计算
let result = item * item * item
lock.sync {
results.append(result)
}
group.leave()
}
}
group.notify(queue: .main) {
completion(results.sorted())
}
}
}
// 使用示例
let manager = DataManager()
manager.processDataAsync(data: [1, 2, 3, 4, 5]) { results in
print("处理结果:\(results)") // [1, 4, 9, 16, 25]
}
实际应用:后台数据处理、文件操作等都需要多线程。
6. 通知中心
import Foundation
class NotificationManager {
init() {
// 监听键盘通知
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillShowNotification,
object: nil,
queue: .main
) { notification in
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
print("键盘高度:\(keyboardFrame.height)")
// 调整UI布局
}
}
// 自定义通知
NotificationCenter.default.addObserver(
self,
selector: #selector(handleCustomNotification(_:)),
name: NSNotification.Name("CustomNotification"),
object: nil
)
}
@objc func handleCustomNotification(_ notification: Notification) {
if let userInfo = notification.userInfo {
print("收到自定义通知:\(userInfo)")
}
}
func postCustomNotification() {
NotificationCenter.default.post(
name: NSNotification.Name("CustomNotification"),
object: self,
userInfo: ["message": "Hello from notification!"]
)
}
}
实际应用:系统通知和应用内组件通信。
7. 错误处理
enum NetworkError: Error {
case invalidURL
case noData
case parsingError
}
func fetchData(from urlString: String, completion: @escaping (Result<Data, NetworkError>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.noData))
return
}
guard let data = data else {
completion(.failure(.noData))
return
}
completion(.success(data))
}.resume()
}
// 使用示例
fetchData(from: "https://api.example.com/data") { result in
switch result {
case .success(let data):
print("获取数据成功,大小:\(data.count) bytes")
// 处理数据
case .failure(let error):
switch error {
case .invalidURL:
print("无效的URL")
case .noData:
print("没有数据")
case .parsingError:
print("解析错误")
}
}
}
实际应用:网络请求、文件操作等可能出错的操作。
8. 依赖注入和配置
class NetworkService {
typealias RequestCompletion = (Result<Data, Error>) -> Void
private let session: URLSession
private let baseURL: URL
init(session: URLSession = .shared, baseURL: URL) {
self.session = session
self.baseURL = baseURL
}
func request(endpoint: String, completion: @escaping RequestCompletion) {
let url = baseURL.appendingPathComponent(endpoint)
session.dataTask(with: url) { data, _, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
}
}
// 配置不同的环境
let developmentConfig = NetworkService(
session: URLSession.shared,
baseURL: URL(string: "https://dev-api.example.com")!
)
let productionConfig = NetworkService(
session: URLSession.shared,
baseURL: URL(string: "https://api.example.com")!
)
实际应用:框架配置、测试mock等。
9. 内存管理和循环引用
class ViewModel {
var data: [String] = []
var onDataUpdate: (() -> Void)?
func loadData() {
// 模拟异步数据加载
DispatchQueue.global().async { [weak self] in // 捕获弱引用
let newData = ["Item 1", "Item 2", "Item 3"]
DispatchQueue.main.async {
self?.data = newData
self?.onDataUpdate?() // 调用闭包
}
}
}
deinit {
print("ViewModel 被释放")
}
}
class ViewController {
let viewModel = ViewModel()
init() {
// 使用 [weak self] 避免循环引用
viewModel.onDataUpdate = { [weak self] in
self?.updateUI()
}
}
func updateUI() {
print("UI已更新")
}
deinit {
print("ViewController 被释放")
}
}
实际应用:MVC/MVVM架构中View和ViewModel的通信。
10. 函数式编程模式
// 函数组合
func compose<A, B, C>(_ f: @escaping (B) -> C, _ g: @escaping (A) -> B) -> (A) -> C {
return { x in f(g(x)) }
}
// 柯里化
func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in { b in f(a, b) } }
}
// 使用示例
let add = { (a: Int, b: Int) -> Int in a + b }
let curriedAdd = curry(add)
let add5 = curriedAdd(5)
let result = add5(3) // 8
let double = { (x: Int) -> Int in x * 2 }
let square = { (x: Int) -> Int in x * x }
let doubleThenSquare = compose(square, double)
let result2 = doubleThenSquare(3) // (3*2)^2 = 36
实际应用:函数式编程风格的代码组织。
练习建议
-
记忆口诀:
- "有尾随,无标签;可省略,推断强"
- "参数多,用$0;单表达式,return省略"
-
常见错误避免:
- 闭包参数类型与函数期望不匹配
- 忘记处理闭包中的错误
- 循环引用导致的内存泄漏
-
调试技巧:
- 使用
print语句调试闭包执行 - 注意闭包捕获的变量作用域
- 使用
weak self避免循环引用
- 使用
闭包逻辑实现的本质分析
核心概念:逻辑定义 vs 逻辑执行
1. 闭包定义了逻辑,函数调用了逻辑
// 函数定义:只负责调用闭包,不实现具体逻辑
func processData(data: [Int], operation: (Int) -> String) {
for item in data {
let result = operation(item) // 这里调用闭包,执行逻辑
print("处理结果:\(result)")
}
}
// 闭包定义:实现具体逻辑
let doubleAndFormat = { (number: Int) -> String in
let doubled = number * 2
return "数字 \(number) 的两倍是 \(doubled)"
}
// 使用:传递闭包给函数
let numbers = [1, 2, 3, 4, 5]
processData(data: numbers, operation: doubleAndFormat)
输出结果:
处理结果:数字 1 的两倍是 2
处理结果:数字 2 的两倍是 4
处理结果:数字 3 的两倍是 6
处理结果:数字 4 的两倍是 8
处理结果:数字 5 的两倍是 10
2. 分析执行流程
func downloadImage(url: String, completion: @escaping (UIImage?) -> Void) {
// 第1步:函数开始执行,准备异步操作
print("开始下载图片:\(url)")
DispatchQueue.global().async {
// 第2步:模拟网络请求(这里不实现具体逻辑)
print("正在从网络获取图片...")
Thread.sleep(forTimeInterval: 1.0)
// 第3步:模拟获取图片成功
let image = UIImage(systemName: "photo")
// 第4步:回到主线程,调用闭包
DispatchQueue.main.async {
print("网络请求完成,调用completion闭包")
completion(image) // 执行闭包:UI更新逻辑在这里执行
}
}
}
// 闭包定义UI更新逻辑
let imageView = UIImageView()
downloadImage(url: "https://example.com/image.jpg") { downloadedImage in
// 第5步:闭包执行,实现UI更新逻辑
print("闭包执行:更新UI")
imageView.image = downloadedImage
imageView.setNeedsDisplay()
}
执行顺序分析:
-
downloadImage函数开始执行(网络请求准备) - 异步执行网络请求(不包含UI逻辑)
- 网络请求完成,回到主线程
-
调用闭包:
completion(image)- 这是函数调用闭包的地方 -
闭包执行:
imageView.image = downloadedImage- 这是UI逻辑实现的地方
关键理解点
谁负责实现逻辑?
-
函数(调用者):
- 定义接口和调用时机
- 负责数据准备和传递
- 决定何时调用闭包
-
闭包(被调用者):
- 实现具体的业务逻辑
- 处理传递的数据
- 执行最终的操作
实际开发中的职责分离
class NetworkManager {
// 函数:只负责网络请求,不负责数据处理
func fetchUserData(completion: @escaping (Result<User, Error>) -> Void) {
// 网络请求逻辑在这里实现
URLSession.shared.dataTask(with: userURL) { data, _, error in
// 数据处理逻辑在这里实现
if let error = error {
completion(.failure(error))
} else if let data = data {
// 解析逻辑在这里实现
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}
}.resume()
}
}
class UserViewController: UIViewController {
private let networkManager = NetworkManager()
func loadUser() {
// UI逻辑在这里实现
networkManager.fetchUserData { [weak self] result in
switch result {
case .success(let user):
// UI更新逻辑在这里实现
self?.updateUI(with: user)
case .failure(let error):
// 错误处理UI逻辑在这里实现
self?.showError(error)
}
}
}
private func updateUI(with user: User) {
nameLabel.text = user.name
ageLabel.text = "\(user.age)岁"
// 更多UI更新逻辑...
}
private func showError(_ error: Error) {
let alert = UIAlertController(title: "错误", message: error.localizedDescription, preferredStyle: .alert)
present(alert, animated: true)
}
}
记忆总结
函数 vs 闭包的职责分工:
| 方面 | 函数(调用者) | 闭包(被调用者) |
|---|---|---|
| 逻辑实现 | 框架逻辑、流程控制 | 具体业务逻辑、数据处理 |
| 执行时机 | 决定何时执行 | 被调用时执行 |
| 关注点 | 何时做、如何传递数据 | 做什么、如何处理数据 |
| 可复用性 | 固定流程,灵活回调 | 每次使用可以不同逻辑 |
经典比喻:
- 函数是"饭店厨师":负责准备食材和烹饪环境
- 闭包是"顾客点的菜谱":告诉厨师具体做什么菜
- 调用闭包是"上菜":厨师按照菜谱做出菜给顾客
代码层面的理解:
// 函数定义了"做什么事"的框架
func doSomething(action: () -> Void) {
print("准备做事")
action() // 具体"怎么做"由闭包决定
print("事情完成")
}
// 闭包实现了"怎么做"的具体逻辑
doSomething {
print("这是我要做的事情") // 具体逻辑在这里
}
这样理解就能清楚:函数提供执行环境和时机,闭包提供具体的执行逻辑。
Swift闭包 vs Objective-C Block 对比
面试常问的核心区别
1. 语言和语法差异
Swift闭包:
// 基本语法
let closure: (Int, Int) -> Int = { (a, b) in
return a + b
}
// 简化语法
let simplified = { $0 + $1 }
// 尾随闭包
func calculate(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
let result = calculate(a: 10, b: 5) { $0 - $1 }
Objective-C Block:
// 基本语法
int (^block)(int, int) = ^(int a, int b) {
return a + b;
};
// 使用
int result = block(10, 5);
// 作为参数传递
- (void)calculateWithA:(int)a b:(int)b operation:(int(^)(int, int))operation {
int result = operation(a, b);
NSLog(@"Result: %d", result);
}
// 调用
[self calculateWithA:10 b:5 operation:^int(int x, int y) {
return x * y;
}];
2. 类型系统和类型推断
| 特性 | Swift闭包 | Objective-C Block |
|---|---|---|
| 类型标注 | 支持类型推断,大多可省略 | 需要明确类型标注 |
| 返回值 | 支持多返回值、元组 | 只能单返回值 |
| 可选类型 | 原生支持Optional | 需要使用指针 |
| 泛型 | 强大泛型支持 | 有限泛型支持 |
3. 内存管理和循环引用
Swift闭包:
class ViewController: UIViewController {
var completionHandler: (() -> Void)?
func setup() {
// 避免循环引用
completionHandler = { [weak self] in
self?.updateUI()
}
// 或使用 unowned(当self一定存在时)
completionHandler = { [unowned self] in
self.updateUI()
}
}
}
Objective-C Block:
@interface ViewController ()
@property (nonatomic, copy) void (^completionHandler)(void);
@end
@implementation ViewController
- (void)setup {
// 避免循环引用
__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
[weakSelf updateUI];
};
// 或使用 __unsafe_unretained
__unsafe_unretained typeof(self) unsafeSelf = self;
self.completionHandler = ^{
[unsafeSelf updateUI];
};
}
@end
4. 变量捕获机制
| 特性 | Swift闭包 | Objective-C Block |
|---|---|---|
| 值捕获 | 自动捕获,支持修改 | 自动捕获,默认const |
| 引用捕获 |
inout关键字 |
__block修饰符 |
| 作用域 | 清晰的作用域规则 | 需要注意block作用域 |
Swift捕获示例:
func createCounter() -> () -> Int {
var count = 0
return {
count += 1 // 可以修改捕获的变量
return count
}
}
Objective-C捕获示例:
int (^createCounter)(void) {
__block int count = 0; // 需要__block修饰符才能修改
return ^int{
count += 1;
return count;
};
}
5. 性能和优化
| 特性 | Swift闭包 | Objective-C Block |
|---|---|---|
| 编译优化 | LLVM优化,更好的内联 | GCC优化,较少内联 |
| 运行时开销 | 较小(值类型优化) | 较大(堆分配) |
| 调试体验 | 更好的错误信息 | 较难调试 |
6. 使用场景和生态
Swift闭包优势场景:
- 现代iOS开发(iOS 8+)
- 函数式编程
- 协议和泛型结合
- SwiftUI开发
Objective-C Block优势场景:
- 遗留代码库维护
- 与C/C++交互
- 底层系统编程
优缺点对比总结
Swift闭包的优点:
- 语法简洁:类型推断、尾随闭包、简化参数
- 类型安全:编译时类型检查,更少运行时错误
- 现代化特性:泛型、协议、Optional等
- 性能更好:优化程度更高
- 易学易用:学习曲线平缓
Swift闭包的缺点:
- 学习成本:需要掌握新语法
- 兼容性:无法直接与Objective-C代码交互
- 迁移成本:从Block迁移需要重写
Objective-C Block的优点:
- 成熟稳定:经过多年验证
- 兼容性好:与C/Objective-C完美集成
- 底层控制:更直接的内存控制
Objective-C Block的缺点:
- 语法复杂:类型标注繁琐
- 类型不安全:容易出现类型错误
- 调试困难:错误信息不清晰
- 现代性不足:缺少泛型等特性
面试回答要点
如果问到选择哪个:
- 新项目:推荐Swift闭包
- 遗留项目:继续使用Block
- 混合项目:视情况而定
核心理解:
- Swift闭包是Block的现代化版本
- 两者解决相同问题,但Swift更优雅
- 掌握两者有助于理解编程范式演进
迁移指南
从Block到Swift闭包的常见转换:
// Objective-C Block
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
if (error) {
[self showError:error];
} else {
[self processData:data];
}
}];
// Swift闭包
fetchData { data, error in
if let error = error {
showError(error)
} else if let data = data {
processData(data)
}
}