iOS ARC 内存管理与循环引用排查

举报
yd_223002268 发表于 2026/09/09 08:40:35 2026/09/09
【摘要】 iOS ARC 内存管理与循环引用排查自动引用计数解决了对象释放时机的大量手工管理问题,但它无法判断两个对象之间的强引用是否仍有业务价值。当引用形成闭环时,即使页面已经退出,对象的引用计数也不会归零。理解所有权关系,比在每个闭包前机械添加 weak 更重要。 一、从对象所有权理解 ARC强引用表示对象拥有另一个对象;弱引用不增加引用计数,并在目标释放后自动变为 nil;无主引用同样不增加引...

iOS ARC 内存管理与循环引用排查

自动引用计数解决了对象释放时机的大量手工管理问题,但它无法判断两个对象之间的强引用是否仍有业务价值。当引用形成闭环时,即使页面已经退出,对象的引用计数也不会归零。理解所有权关系,比在每个闭包前机械添加 weak 更重要。

一、从对象所有权理解 ARC

强引用表示对象拥有另一个对象;弱引用不增加引用计数,并在目标释放后自动变为 nil;无主引用同样不增加引用计数,但默认假设目标在访问时一定存在。

常见的父子关系中,父对象强持有子对象,子对象通过弱引用回到父对象:

protocol DetailViewDelegate: AnyObject {
    func detailViewDidClose(_ view: DetailView)
}

final class DetailView: UIView {
    weak var delegate: DetailViewDelegate?
}

协议声明继承 AnyObject 后,代理属性才能使用弱引用。代理是否应该为弱引用,取决于所有权设计,而不只是命名习惯。

二、闭包为什么容易产生循环引用

对象强持有闭包,闭包又捕获对象,就会形成环。

final class ProfileViewController: UIViewController {
    private let viewModel: ProfileViewModel

    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.onStateChanged = { [weak self] state in
            self?.render(state)
        }
    }
}

这里 viewModel 被控制器持有,onStateChanged 又是长期保存在 viewModel 中的闭包,因此弱捕获是合理的。

但并非所有闭包都需要弱捕获。一个立即执行且不被保存的闭包,不会长期持有 self。过度使用弱引用可能让必要操作无声跳过,反而掩盖生命周期问题。

三、根据闭包寿命选择捕获方式

可以把闭包分为三类:

非逃逸闭包

闭包在函数返回前执行完毕,通常直接捕获即可。

短期异步闭包

闭包在一次操作完成后释放。是否弱捕获要看对象是否应该被操作保活。例如保存操作需要在控制器退出后继续完成,就可能有意强持有负责保存的服务,但不一定需要持有控制器。

长期保存闭包

回调属性、观察者和定时器会长期存在,应明确解除时机,并检查是否与持有者形成闭环。

判断标准应是业务所有权:闭包执行期间,目标对象是否必须存活;如果目标已释放,操作应该取消、继续,还是交给其他对象完成。

四、定时器与显示刷新任务

重复定时器经常同时存在两个问题:计时器持有回调,外部对象又持有计时器。即使闭包使用弱引用,也应在任务结束或对象释放时主动停止计时器,避免无意义唤醒。

final class CountdownController {
    private var timer: Timer?
    private(set) var remaining: Int = 0

    func start(seconds: Int) {
        stop()
        remaining = seconds
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
            [weak self] timer in
            guard let self = self else {
                timer.invalidate()
                return
            }
            self.tick()
        }
    }

    func stop() {
        timer?.invalidate()
        timer = nil
    }

    deinit {
        stop()
    }
}

项目最低系统版本不支持某种语法或能力时,应使用项目既有写法,而不能仅为了简洁提高兼容要求。

五、观察者的注册与移除

系统通知、键值观察和自定义事件总线的持有方式并不完全相同,不能套用同一条规则。使用基于闭包的观察接口时,通常需要保存返回的令牌,并在适当时机移除。

final class ThemeObserver {
    private var token: NSObjectProtocol?

    func start() {
        token = NotificationCenter.default.addObserver(
            forName: .themeDidChange,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            self?.handleThemeChange(notification)
        }
    }

    deinit {
        if let token = token {
            NotificationCenter.default.removeObserver(token)
        }
    }
}

自定义事件组件应遵循项目原有约定,明确发布者、订阅者和令牌分别由谁持有。

六、异步任务也有持有关系

对象持有任务,任务闭包强捕获对象,也可能形成持续到任务结束的引用关系。无限序列或长期监听任务如果没有取消点,就可能让对象长期无法释放。

final class MessageObserver {
    private var observeTask: Task<Void, Never>?

    func start() {
        observeTask = Task { [weak self] in
            guard let stream = self?.messageStream else { return }
            for await message in stream {
                guard !Task.isCancelled else { return }
                await self?.handle(message)
            }
        }
    }

    func stop() {
        observeTask?.cancel()
        observeTask = nil
    }

    deinit {
        observeTask?.cancel()
    }
}

任务是否会自然结束、取消是否能传递到数据源,都需要在设计时确认。

七、建立可重复的排查流程

发现页面疑似泄漏时,可以按以下顺序排查:

  1. 在控制器和页面模型的 deinit 中增加临时调试记录。
  2. 重复进入并退出页面,确认对象数量是否持续增长。
  3. 查看内存对象之间的强引用路径。
  4. 优先检查闭包属性、代理、定时器、观察者和长期异步任务。
  5. 修复所有权关系后重复原路径,确认对象能够释放。

只观察某一次内存峰值并不能证明泄漏。图片缓存和系统缓存可能暂时保留内存,关键是对象是否在可预期时机失去强引用,以及重复操作后数量是否持续增长。

八、常见误区

所有闭包都使用 weak self

这会把本该完成的逻辑变成可选执行。应先判断闭包是否逃逸、由谁保存,以及对象是否应该被任务保活。

deinit 没打印就是控制器泄漏

调试环境、转场容器和交互式返回都可能改变释放时机。需要结合引用关系和重复操作验证。

用 unowned 解决所有循环引用

无主引用在目标已释放时访问会导致运行时错误。只有生命周期关系能够被严格保证时才适合使用。

总结

ARC 问题本质上是所有权问题。明确谁拥有谁、回调保存多久、异步任务何时结束,再选择强引用、弱引用或无主引用,比机械添加捕获列表更可靠。配合可重复的页面进出路径和引用图分析,可以快速定位真正的循环链路。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。