Swift 4结构体mutating函数闭包捕获self报错求助
这个问题我之前也碰到过,本质是Swift值类型和逃逸闭包的特性冲突导致的,我来给你拆解一下原因和解决办法:
为什么会报错?
结构体是值类型,mutating方法里的self其实是一个inout参数(可以理解为当前实例的可变临时引用)。而URLSession.dataTask的闭包是逃逸闭包——它会在方法执行结束后才异步运行,Swift不允许逃逸闭包隐式捕获inout类型的self:
- 一方面,
inout参数的生命周期只限于方法执行期间,闭包异步执行时原self的上下文已经不存在了; - 另一方面,值类型的捕获会生成副本,闭包里修改的只是副本,根本不会影响原实例。
你把实例从let改成var没用,是因为问题核心不在实例是否可变,而在mutating方法的self无法被异步闭包捕获。
解决方案
方案一:将结构体改为类(推荐,适配异步场景)
类是引用类型,不需要mutating关键字,闭包可以直接捕获实例引用,修改的就是原对象的属性,完美适配异步操作场景。修改后的代码如下:
class Workspace: Decodable { var guid: String var name: String private enum CodingKeys : String, CodingKey { case guid = "guid" case name = "name" } init(){ self.guid = "blbl" self.name = "oops" } func getUserWorkspace(base: String, completed: @escaping () -> ()){ guard let url = URL(string: "some url") else { print("Invalid URL") return } var request = URLRequest(url: url) request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Accept") URLSession.shared.dataTask(with: request){ [weak self] (data, response, error) in guard let self = self else { return } // 避免循环引用,同时确保实例未被销毁 if error == nil { do { guard let data = data else { print("No data received") return } let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data) self.guid = res.guid self.name = res.name DispatchQueue.main.async { completed() } }catch { print ("JSON error: \(error.localizedDescription)") } } else { print("Request error: \(error!.localizedDescription)") } }.resume() } }
我额外加了几个实用优化:
[weak self]捕获列表,避免循环引用(虽然URLSession任务完成后会自动释放闭包,但养成好习惯总是没错的)- 增加URL和data的可选绑定,避免强制解包的崩溃风险
- 补充了详细的错误打印,方便调试
方案二:保持结构体,通过闭包返回新实例(适合必须用结构体的场景)
如果一定要用结构体,因为值类型无法在异步闭包里修改原实例,我们可以把逻辑改成:异步请求完成后生成新的Workspace实例,通过闭包返回给调用者,由调用者替换原实例。修改后的代码如下:
struct Workspace: Decodable { var guid: String var name: String private enum CodingKeys : String, CodingKey { case guid = "guid" case name = "name" } init(){ self.guid = "blbl" self.name = "oops" } // 去掉mutating,改为返回新实例的闭包 func getUserWorkspace(base: String, completed: @escaping (Workspace?) -> ()){ guard let url = URL(string: "some url") else { print("Invalid URL") completed(nil) return } var request = URLRequest(url: url) request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Accept") URLSession.shared.dataTask(with: request){ (data, response, error) in if error == nil { do { guard let data = data else { print("No data received") completed(nil) return } let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data) DispatchQueue.main.async { completed(res) // 返回新实例 } }catch { print ("JSON error: \(error.localizedDescription)") completed(nil) } } else { print("Request error: \(error!.localizedDescription)") completed(nil) } }.resume() } }
调用时需要这样处理:
var workspace = Workspace() workspace.getUserWorkspace(base: "your_base64_token") { newWorkspace in guard let newWorkspace = newWorkspace else { return } workspace = newWorkspace // 替换原实例 // 这里做后续操作 }
内容的提问来源于stack exchange,提问作者Mika
相关产品推荐
相关产品推荐

