iOS自定义键盘扩展内存泄漏求助:Persistent ViewController对象递增
我之前也碰到过一模一样的问题!结合你用Instruments排查的情况,这个Persistent ViewController对象递增的问题,其实是iOS自定义键盘扩展里挺常见的内存泄漏场景,我来给你捋捋原因和实操解决办法:
问题根源分析
自定义键盘扩展的生命周期和宿主应用深度绑定,每次键盘隐藏再显示,系统会重新触发键盘的初始化流程。但如果你的UIInputViewController(也就是键盘的主控制器)被强引用循环或者外部强引用持有,ARC就无法正常回收旧的控制器实例,导致每次操作都会新增一个无法释放的对象,最终造成内存堆积。而且真机的内存管理策略比模拟器严格得多,所以这个问题只会在真机上暴露出来。
具体解决方案
1. 排查并修复强引用循环
这是最常见的原因,重点检查这几个地方:
- 键盘控制器里的闭包有没有用
[weak self]或者[unowned self]?如果闭包捕获了self但没做弱引用,很容易形成循环引用。 - 有没有把键盘控制器实例赋值给全局变量、单例的强引用属性?比如某些工具类里存了
static var currentKeyboardVC: UIInputViewController?,这种写法直接就把控制器死死持有了。 - 子视图或子控制器有没有被父控制器以外的对象强引用?比如某个自定义视图里存了
var parentVC: UIInputViewController,而父控制器又持有这个视图,就会形成循环。
2. 在生命周期方法里主动清理资源
键盘控制器的viewWillDisappear:、didReceiveMemoryWarning:是清理资源的关键节点,一定要在这里做主动释放:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 清空所有闭包引用,切断循环 self.someAsyncCompletion = nil // 销毁定时器、观察者 self.keyboardTimer?.invalidate() self.keyboardTimer = nil NotificationCenter.default.removeObserver(self) // 清理子控制器和子视图的强引用 self.childViewControllers.forEach { $0.willMove(toParent: nil) $0.view.removeFromSuperview() $0.removeFromParent() } }
3. 不要强行持有键盘控制器实例
别试图把键盘控制器做成“单例”或者持久化存储,系统会根据键盘的激活状态自动管理控制器的创建和销毁。强行持有旧实例只会让它们无法被ARC回收,进而导致对象数量递增。
4. 用Instruments精准定位泄漏点
你已经用了Allocations工具,接下来可以用Leaks工具进一步深挖:
- 打开Instruments,选择Leaks模板
- 运行键盘扩展,多次切换应用、隐藏/显示键盘
- 点击工具栏的「Mark Generation」按钮,对比每次生成的对象差异
- 找到Persistent ViewController的引用链,就能清楚看到是谁在持有它不释放
额外提醒
真机上的内存阈值比模拟器低很多,如果泄漏问题不解决,长时间使用后键盘扩展很可能会被系统强制杀掉,影响用户体验。一定要在真机上做完整的内存测试,不要只依赖模拟器。
内容的提问来源于stack exchange,提问作者Oak Soe Shein
相关产品推荐
相关产品推荐

