异步网络请求的逃逸闭包中,是否应使用weak self捕获?
self的捕获:weak还是strong? 先直接给结论:当前你的场景下不会形成循环引用,但从代码健壮性和最佳实践出发,推荐使用weak self。下面来一步步拆解原因:
1. 为什么你测试中ViewModel的deinit能正常调用?
你担心的循环引用链是:ViewModel → service → success闭包 → ViewModel,但这个链要形成内存泄漏的前提是所有引用都是强引用,且这个链不会被自动打破。
实际你的fetchProducts方法应该是这样的实现逻辑:发起网络请求时,把闭包暂存起来,当请求完成(成功/失败)后,立刻执行闭包并将其释放。也就是说,service对闭包的持有是临时的——请求结束后,闭包就被销毁了,这条引用链也就断了。所以当持有ViewModel的ViewController被销毁时,ViewModel的引用计数能降到0,deinit自然会被调用,不会有内存泄漏。
2. 什么时候需要用weak self?
只有当service会长期持有这个success闭包时(比如把闭包存在全局数组、自己的存储属性里,即使请求完成也不释放),才会形成永久的强引用闭环,这时候必须用weak self来打破循环。
但问题是:你现在的service实现是临时持有,但未来如果有人修改service的逻辑(比如加了闭包缓存机制),那原本没问题的代码就会突然出现内存泄漏。所以从代码健壮性考虑,提前用weak self是更稳妥的做法。
另外还有一种极端场景:如果网络请求耗时非常久,而用户在请求完成前就退出了ViewController(ViewModel被标记为可销毁),这时候如果用strong self,闭包会强持有ViewModel,导致它无法及时释放,直到请求完成——虽然这不是永久泄漏,但会延迟内存释放,也是不推荐的。
3. 正确的写法示例
推荐使用weak self配合guard let来确保安全:
service.fetchProducts(parameters: params, success: { [weak self] response in guard let self = self else { // 这里可以做一些清理,比如忽略过期的请求结果 return } self.isLoading?(false) // 处理响应数据 })
如果你能100%确定ViewModel在闭包执行时一定存在(比如请求发起后ViewModel不会被销毁),也可以用unowned self,但weak self的容错性更强,是更通用的选择。
总结
- 当前你的代码没有循环引用,是因为
service对闭包的持有是临时的,请求完成后引用链就断了; - 但为了避免未来代码变更带来的潜在问题,以及处理极端场景下的延迟释放,推荐始终在逃逸闭包中使用
weak self捕获ViewModel; - 循环引用的核心是「永久的强引用闭环」,临时的引用链不会导致内存泄漏,但提前做好防御性编码总是没错的。
内容的提问来源于stack exchange,提问作者neo

