Swift 4迁移报错:逃逸闭包只能显式按值捕获inout参数
这个问题我在Swift版本迁移时也碰到过,本质是Swift 3及后续版本为了内存安全,对逃逸闭包捕获inout参数的规则做了严格限制。我来给你拆解下原因和解决办法:
错误原因
inout参数本质是按引用传递,但逃逸闭包可能在函数返回后才执行,这时候原变量的生命周期可能已经结束,Swift为了避免潜在的内存问题和语义模糊,要求必须显式处理inout参数的捕获,不能像Swift 2那样隐式捕获。
解决方案:用包装类传递可变任务引用
最稳妥的方式是创建一个简单的包装类,把URLSessionTask?包装成引用类型,这样就能在逃逸闭包里安全地修改任务引用,同时外部调用者也能拿到最新的任务实例:
首先定义包装类:
class TaskHolder { var task: URLSessionTask? }
然后修改你的两个函数,替换掉原来的inout参数:
func fetchUserThumbnailAvatar(_ taskHolder: TaskHolder, completion: @escaping (_ image: UIImage?) -> ()) { let internalUrl = Session.currentUser?.avatar?.thumbnailURL ?? URL(string: "")! let externalUrl = URL(string: thumbnailAvatar) ?? URL(string: "")! fetchUserAvatar(internalUrl, externalUrl: externalUrl, taskHolder: taskHolder, completion: completion) } fileprivate func fetchUserAvatar(_ internalUrl: URL, externalUrl: URL, taskHolder: TaskHolder, completion: @escaping (_ image: UIImage?) -> ()) { fetchImage(externalUrl, taskHolder: taskHolder) { image in if image == nil { // 外部图加载失败时,切换加载内部图,同时更新任务引用 self.fetchImage(internalUrl, taskHolder: taskHolder, completion: completion) } else { self.cache.removeObject(forKey: internalUrl.path as NSString) completion(image) } } }
同时你需要同步修改fetchImage函数的参数,把原来的inout URLSessionTask?改成TaskHolder,比如:
fileprivate func fetchImage(_ url: URL, taskHolder: TaskHolder, completion: @escaping (_ image: UIImage?) -> ()) { let task = URLSession.shared.dataTask(with: url) { data, response, error in let image = data.flatMap { UIImage(data: $0) } DispatchQueue.main.async { completion(image) } } taskHolder.task = task task.resume() }
备选方案:显式按值捕获(仅适用于不需要更新原任务的场景)
如果你的场景中不需要外部调用者拿到内部图加载的任务,只是想让代码编译通过,可以显式捕获task的当前值,但这样修改内部localTask不会同步到原参数:
fileprivate func fetchUserAvatar(_ internalUrl : URL, externalUrl : URL,_ task : inout URLSessionTask?, completion : @escaping (_ image : UIImage?) -> ()) { fetchImage(externalUrl, task: &task, completion: { [task] image in if image == nil { var localTask = task self.fetchImage(internalUrl, task: &localTask, completion: completion) } else { self.cache.removeObject(forKey: internalUrl.path as NSString) completion(image) } }) }
不过这个方案有局限性,外部无法取消内部图的加载任务,所以更推荐第一个方案。
内容的提问来源于stack exchange,提问作者vjeta
相关产品推荐
相关产品推荐

