Swift 4:如何从类变量访问completionHandler返回值?
解决Completion Handler赋值外部变量失效的问题
兄弟,你遇到的这个问题99%是异步执行顺序搞的鬼,不是变量作用域的问题!先给你理清楚核心原因:
带completionHandler的方法大多是异步的——也就是说,你调用这个方法后,代码会立刻往下走,而completion里的逻辑要等异步任务(比如网络请求、文件读写)完成后才会执行。这时候如果你在调用方法后立刻去访问外部变量,completion里的赋值代码根本还没跑,自然拿不到值。
先看一个典型的错误示例(以Swift为例)
class DataManager { var fetchedData: String? // 类变量 func loadRemoteData() { // 模拟一个异步网络请求 URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { [weak self] data, _, _ in if let data = data { self?.fetchedData = String(data: data, encoding: .utf8) print("Completion里的赋值完成:\(self?.fetchedData ?? "空")") } }.resume() } } // 调用代码 let manager = DataManager() manager.loadRemoteData() print("外部访问fetchedData:\(manager.fetchedData ?? "空")") // 这里会打印"空"!
运行后你会发现,外部打印的语句先执行,completion里的打印后执行——这就是顺序问题!
给你几个靠谱的解决思路:
1. 把需要用值的逻辑直接放到Completion里
这是最简单直接的方式,既然completion是拿到值的地方,那所有依赖这个值的操作都在这里执行:
func loadRemoteData() { URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { [weak self] data, _, _ in if let data = data { self?.fetchedData = String(data: data, encoding: .utf8) // 在这里处理数据,比如更新UI、存本地等等 DispatchQueue.main.async { self?.updateUI(with: self?.fetchedData) } } }.resume() } func updateUI(with data: String?) { // 更新界面逻辑 }
2. 用Async/Await替代Completion(现代语言都支持)
如果你的项目用的是Swift 5.5+、Kotlin、JavaScript等支持async/await的语言,直接把异步方法改成async函数,就能像同步代码一样处理:
func loadRemoteData() async -> String? { do { let (data, _) = try await URLSession.shared.data(from: URL(string: "https://example.com")!) return String(data: data, encoding: .utf8) } catch { print("请求失败:\(error)") return nil } } // 调用的时候用Task包裹(Swift里) Task { let manager = DataManager() let result = await manager.loadRemoteData() manager.fetchedData = result print("拿到数据:\(result ?? "空")") // 这里能正确拿到值 }
3. 自定义回调闭包传递结果
如果不想用async/await,也可以给你的方法加一个自定义回调,让调用方自己处理结果:
func loadRemoteData(completion: @escaping (String?) -> Void) { URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { data, _, _ in let result = data.flatMap { String(data: $0, encoding: .utf8) } completion(result) }.resume() } // 调用的时候 let manager = DataManager() manager.loadRemoteData { result in manager.fetchedData = result print("拿到数据:\(result ?? "空")") // 在这里做后续处理 }
额外提醒:
- 记得用
[weak self]避免循环引用,尤其是在闭包里引用self的时候; - 如果要更新UI,一定要切回主线程(比如Swift里的
DispatchQueue.main.async); - 永远不要假设异步任务会立刻完成,所有依赖异步结果的逻辑都要等回调触发后再执行。
内容的提问来源于stack exchange,提问作者batuhangundogdu
相关产品推荐
相关产品推荐

