iOS隐藏自定义UIWindow后内存未释放 需置nil才触发deinit回收
问题现象
在iOS开发中通过自定义UIWindow实现特定页面承载展示时,调用自定义hide方法隐藏窗口后,UIWindow实例不会被系统回收,其持有的根视图控制器也不会执行deinit完成内存释放。
相关代码实现
自定义窗口SDKWindow实现
class SDKWindow: UIWindow { // 用于在容器中查找SDKWindow的唯一标识,可遍历所有窗口找到该sdk窗口完成控制器的显示/隐藏操作 static let SDKWindowTag = 101011 init(rootController: UIViewController) { if #available(iOS 13.0, *) { let windowScene = UIApplication.shared .connectedScenes .filter { $0.activationState == .foregroundActive } .first if let windowScene = windowScene as? UIWindowScene { super.init(windowScene: windowScene) } else { super.init(frame: UIScreen.main.bounds) } } else { super.init(frame: UIScreen.main.bounds) } self.rootViewController = rootController self.windowLevel = .statusBar - 1 self.tag = SDKWindow.SDKWindowTag self.accessibilityLabel = "SDK.Window" self.accessibilityViewIsModal = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func show() { printLog("Show sdkwindow") self.makeKeyAndVisible() if self.isHidden == false { return } self.isHidden = false self.alpha = 1.0 } func hide() { if #available(iOS 13.0, *) { self.windowScene = nil } else { self.rootViewController = nil } self.alpha = 0.0 self.isHidden = true self.resignKey() self.removeFromSuperview() } }
窗口调用侧代码
初始实现代码如下:
@objc public class MyClass: NSObject { /// 私有变量 private var popupWindow: SDKWindow? public func showWindow() { let viewController : CustomController = CustomController.init() self.popupWindow = SDKWindow.init(rootController: viewController) self.popupWindow?.show() } public func hide() { self.popupWindow?.hide() } }
初始实现下调用hide方法时,CustomViewController的deinit始终不会触发;如果修改hide方法,在调用popupWindow的hide逻辑后额外将popupWindow属性置为nil,修改后代码如下:
public func hide() { self.popupWindow?.hide() self.popupWindow = nil }
修改后CustomViewController的deinit会正常调用,UIWindow实例也会被正常回收内存。
原因解答
仅调用自定义hide方法无法释放内存的核心原因
iOS内存回收基于ARC自动引用计数机制,只有当对象的强引用计数降到0时,实例才会被系统回收。
MyClass中用强引用属性popupWindow持有了SDKWindow实例,这是一个独立于系统视图层级的业务侧强引用。- 自定义
hide方法做的操作,只是把窗口从系统视图层级移除、设置隐藏状态、断开和windowScene的关联、旧版本下释放rootViewController引用,完全没有断掉业务侧属性对SDKWindow实例本身的强持有。 - 只要
popupWindow属性还指向这个SDKWindow实例,实例的引用计数就不会降到0,自然不会被回收;而SDKWindow本身会默认强持有它的rootViewController,所以根控制器也不会被释放,deinit自然不会触发。 - 手动把
popupWindow置为nil后,MyClass对窗口实例的强引用被断开,此时如果没有其他地方强持有这个窗口,它的引用计数就会降到0被回收,它持有的根视图控制器引用也会随之释放,控制器的deinit就会正常执行。
注意:即使调用了
removeFromSuperview(),系统也只会管理视图层级内的引用,不会帮你自动断开业务代码里自己添加的强持有关系,所有业务侧创建的强引用都需要手动断开,不要依赖系统视图管理逻辑释放自定义对象。
当前hide方法的实现遗漏
- 清理逻辑的版本分支不合理:iOS13+版本下只设置了
windowScene = nil,但没有手动把rootViewController置为nil,如果存在其他未断开的窗口引用,根控制器依然会被窗口强持有无法释放。 show方法存在逻辑顺序问题:先调用了makeKeyAndVisible(),之后才判断isHidden状态提前返回,会导致重复调用show时重复执行makeKeyAndVisible逻辑,还可能触发不必要的键盘、状态栏状态切换。- 方法设计没有明确持有关系约定:没有在接口逻辑中提示调用方需要释放自己持有的窗口实例,很容易造成漏写置空逻辑引发内存泄漏。
内容的提问来源于stack exchange,提问作者Himan Dhawan
相关产品推荐
相关产品推荐

