You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 09:54:23