关于OC与Swift内存管理的解惑

  • 在Swift中,如何解决闭包的循环引用?
js 复制代码
myClosure = { [weak self] in
    guard let self = self else { return }
}
  • 那么如果是多个闭包嵌套呢?
js 复制代码
// ✅ 只需要在最外层写一次 
myClosure1 = { [weak self] in [weak self]
    // 在顶部进行一次安全解包
    guard let self = self else { return }
    // 在这个作用域里,`self` 是一个临时的强引用,可以安全使用
    // 不用担心它被提前释放
    myClosure2 = {
        // 它会捕获上面 guard let 创建好的、非可选的强引用 self
        myClosure3 = {
            // 你可以直接、安全地调用 self 的方法或属性
        }
    }
}
  • 那么又有一个问题:在OC中的block,就需要每一层都要弱引用,第二层要先强引用再弱引用,第三层再先强引用再弱引用吧?为什么Swift不是这个逻辑? 这是因为Swift 的逻辑确实和 OC 不一样,它在这方面做了极大的简化和安全性提升。

OC 的核心问题在于: @strongify(self) 创建的那个临时强引用 strongSelf 的生命周期会持续到包含它的那个 block 执行完毕 。如果这个 block 内部又启动了一个长时间的异步任务(第二个 block),那么 strongSelf 会被第二个 block 捕获,导致 self 实例的生命周期被不必要地延长。因此,为了追求最精细的内存管理,开发者才会在每一层异步调用前都重复进行"弱化 -> 强化"的操作。

Swift 的优势在于: guard let self = self 创建的强引用 self 是一个全新的、仅存在于外层闭包作用域内的局部变量 。当 anotherAsyncTask 的闭包(内层闭包)创建时,它捕获的是这个局部的、新的 self 。一旦外层闭包执行完毕(someAsyncTask 的回调结束),这个局部的 self 变量就会被销毁。anotherAsyncTask 的闭包对它的持有也就自然解除了,完全不会影响到原始 self 实例的生命周期。

特性 / 行为 Objective-C (block) Swift (closure)
弱引用声明 @weakify(self)__weak typeof(self) weakSelf = self; 在捕获列表 [weak self]
临时强引用 @strongify(self)__strong typeof(weakSelf) strongSelf = weakSelf; guard let self = self else { return }
嵌套捕获 内层 block 捕获由 @strongify 创建的 strongSelf,其生命周期可能过长,导致需要"再次弱化"。 内层闭包捕获由 guard let 创建的局部强引用 self 。该局部变量生命周期很短,因此无需再次弱化
开发者操作 需要警惕并可能在每一层异步调用前都重复"弱化-强化"的模式。 只需要在最外层做一次"弱化-强化" ,内部可以完全放心使用。
  • 但是在实际开发中,swift代码提示引用报错,往往xcode是这样解决的,为什么?
js 复制代码
someClosure = { [self] in }

那是因为Xcode 的首要任务是解决编译错误,而不是帮你分析内存管理。[self] in 正是解决这个特定编译错误的"最直接"的语法。它解决了语法问题 ,但它没有解决循环引用的问题 。它只是把一个隐式的强引用,变成了一个显式的强引用。所以解决循环引用问题还是需要[weak self]

  • 那么在实际开发中[self]对于内存泄露来说是错误的呗?

这个说法不完全准确,但您的警惕性非常对!更精确的说法是: 在【会】产生循环引用的场景下使用 [self],是绝对错误的,它会直接导致内存泄漏。 但是,在【不会】产生循环引用的场景下,[self] 则是安全、甚至是被推荐的写法。 所以,[self] 本身不是"错误",它只是一个工具。错误的是在不合适的场景下使用了这个工具。

比如下列两种情况

js 复制代码
// 这个闭包被传递给 UIView.animate,执行完动画后就会被销毁。 
// self 并没有一个属性来持有这个闭包。 
// 所以 self -> 闭包 这条强引用链不存在。 
UIView.animate(withDuration: 0.5) { [self] in 
    // 在这里使用 [self] in 是【完全正确】的。 
    // 它明确地告诉编译器:"我知道我在强引用 self,且我确定这是安全的。" 
}

// DispatchQueue.main.asyncAfter 的闭包同样是执行完就销毁。 
DispatchQueue.main.asyncAfter(deadline: .now() +1.0) { [self] in 
    // 这里使用 [self] in 也是【完全正确】的。 
}

简单的可以理解为"一次性"的工作 (用 [self] 是安全的),"长期"的规则 (必须用 [weak self])。

  • 那么又有一个场景,在对网络请求进行二次封装的情况下,在调用网络请求时,是否需要弱引用?

答案是不需要的。为什么呢?因为Alamofire临时持有了您的闭包,由于 ViewController 没有持有任何东西,所以闭包无论如何强引用 ViewController,都构不成一个闭环。因此,这里使用 [self] 来显式强引用是完全安全的。

  • 为什么感觉 OC 的 AFNetworking 封装调用时不需要弱引用?

无论是在 OC 的 AFNetworking 还是 Swift 的 Alamofire,它们【内部都没有,也不可能】自动处理您在闭包中捕获 self 导致的循环引用。防止循环引用的责任始终在调用者(也就是您)身上。

  • 那为什么您会有"AFN不需要弱引用"的印象呢?

原因和我们上面分析的完全一样:因为您在 OC 中调用 AFN 封装的场景,很可能也属于"一次性"的调用,本身就不会产生循环引用。由于AFHTTPSessionManager的实例 manager 是一个局部变量,方法执行完就释放了,success block 被 manager 临时持有,执行完也就释放了。所以,当时您在 OC 里不写弱引用是正确的,不是因为 AFNetworking 内部处理了,而是因为您的【用法】决定了它根本没有循环引用!

js 复制代码
[MyOCNetworkManager requestWithURL:@"" parameters:nil success:^(id responseObject) { 
    // 您在这里直接使用 self,比如 [self.tableView reloadData]; 
    // 并没有写 __weak typeof(self) weakSelf = self; 
} failure:^(NSError *error) { 
    // ... 
}];
  • 那么在OC+AFNetworking的二次封装回调时,如果我依然写弱引用的话,会有问题吗?

完全没有问题,这样做在内存上是绝对安全的,但同样,它也是不必要的,并且有轻微的副作用。 在那个场景下,编写弱引用代码(即 weak/strong dance)是"安全但多余的"。

我们来对比一下两种写法:

  1. 在您调用一个"一次性"的网络请求封装时,闭包不会被您的 ViewController 持有,因此不存在循环引用。
js 复制代码
[MyOCNetworkManager requestWithURL:@"" 
                        parameters:nil 
                           success:^(id responseObject) {
    // 直接使用 self,闭包会强引用 self。
    // 因为没有循环引用,self 会在闭包执行完后被正常释放,这是安全的。
    [self.tableView reloadData];
} 
                           failure:nil];

这种写法简洁、清晰,并且正确地表达了意图:"这是一个一次性的任务,我需要 self 在任务执行时是存在的。"

  1. 如果您坚持使用弱引用,代码会是这样:
js 复制代码
__weak typeof(self) weakSelf = self;
[MyOCNetworkManager requestWithURL:@"/some/path" 
                        parameters:nil 
                           success:^(id responseObject) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) {
        return;
    }
    // 使用 strongSelf 来确保在闭包执行期间 self 不会被释放
    [strongSelf.tableView reloadData];
} 
                           failure:nil];
  1. 内存安全吗?

是的,100%安全。弱引用永远不会增加引用计数,所以它绝不会"创造"出一个循环引用。从这个角度看,它没有任何"问题"。

  1. 有什么副作用或缺点吗?

有,和 Swift 的情况完全相同:

代码变得冗余:为了一个没有必要的安全措施,您多写了三行样板代码 (__weak, __strong, if)。这降低了代码的简洁性。

意图变得模糊:当其他开发者读到这段代码时,他们会看到 weak/strong dance,这通常是一个强烈的信号,表示"这里有循环引用的风险"。他们可能会因此花时间去寻找一个实际并不存在的风险点,增加了维护成本。

极端情况下的功能差异:如果 self (比如一个 UIViewController) 在网络请求发出后、但在回调执行前被释放了(例如用户快速返回上一个页面),那么:

写法一(强引用) :self 会被闭包"续命",直到闭包执行完毕。[self.tableView reloadData] 会被执行。

写法二(弱引用) :self 会被立即释放。当回调执行时,strongSelf 会是 nil,代码会直接 return[strongSelf.tableView reloadData] 不会被执行。

js 复制代码
[MyOCNetworkManager requestWithURL:@"" 
                        parameters:nil 
                           success:^(id responseObject) {
    // 直接使用 self,闭包会强引用 self。
    // 因为没有循环引用,self 会在闭包执行完后被正常释放,这是安全的。
    [self.tableView reloadData];
} 
                           failure:nil];
js 复制代码
 __weak typeof(self) weakSelf = self;
[MyOCNetworkManager requestWithURL:@"" 
                        parameters:nil 
                           success:^(id responseObject) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) {
        return;
    }
    // 使用 strongSelf 来确保在闭包执行期间 self 不会被释放
    [strongSelf.tableView reloadData];
} 
                           failure:nil];
  • 极端情况下的功能差异:如果 self (比如一个 UIViewController) 在网络请求发出后、但在回调执行前被释放了(例如用户快速返回上一个页面),写法二(弱引用):self 会被立即释放。当回调执行时,strongSelf 会是 nil,代码会直接 return,[strongSelf.tableView reloadData] 不会被执行。 但是实际中的这种写法很常见,我以前就是这么写,但是没有发现什么问题啊?返回页面还是会执行dealloc‌,为什么?
  1. 大多数情况下,行为差异"无关紧要"

我们回到最常见的场景:网络请求回来后,更新界面。

Objective-C

objectivec 复制代码
// 弱引用写法
__weak typeof(self) weakSelf = self;
[MyNetworkManager request:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (!strongSelf) {
        return; // 如果 self 销毁了,就直接返回
    }
    [strongSelf.tableView reloadData]; // 更新界面
}];

设想一下用户快速返回,self (也就是 ViewController) 被销毁了。这时网络回调回来了,因为 strongSelfnil,所以 [strongSelf.tableView reloadData] 这行代码没有被执行**。

这对用户来说是问题吗?完全不是。 因为界面都已经消失了,tableView 也不存在了,去刷新一个不存在的界面本来就是一件没有意义的事情。代码不执行,反而更干净利落。

所以,在 99% 的 UI 更新场景中,弱引用导致的"代码不执行"这个行为差异,不仅不是问题,反而是我们期望的、最合理的结果。

  1. "快速返回"的极端情况发生概率低

要触发这个"功能差异",需要满足一个条件:self 的销毁发生在"网络请求发出后"和"回调执行前"这个短暂的时间窗口内。

对于大多数响应速度很快的 API 来说,这个窗口可能只有几百毫秒。用户需要操作得非常快才能正好卡在这个时间点上。因此,在日常测试和使用中,这个情况本身就不容易遇到。

  1. 即使代码不执行,也无可见负面影响

假设回调里做的事情是 [self hideLoadingIndicator]。如果用户已经返回了上一个页面,那个加载指示器 loadingIndicator 本来就已经随着页面消失了,所以 hideLoadingIndicator 这行代码执不执行,用户根本感知不到任何区别。

相关推荐
美狐美颜sdk2 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭6 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
泓博7 小时前
Objective-c把字符解析成字典
开发语言·ios·objective-c
Daniel_Coder8 小时前
Xcode 中常用图片格式详解
ios·xcode·swift
瓜子三百克8 小时前
Objective-C 路由表原理详解
开发语言·ios·objective-c
帅次8 小时前
Objective-C面向对象编程:类、对象、方法详解(保姆级教程)
flutter·macos·ios·objective-c·iphone·swift·safari
RyanGo11 小时前
iOS断点下载
ios·swift
蒙小萌199311 小时前
找工作-iOS开发-3年经验-AI协作开发
ios
杂雾无尘14 小时前
掌握生死时速:苹果应用加急审核全攻略!
ios·swift·apple