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

从vcC弹出UIViewController时内存溢出崩溃问题求助

问题描述
  • App导航结构:UINavigationController -> vcA -> vcB -> vcC
  • 从vcC弹出任意UIViewController时,内存持续增长直至out-of-memory崩溃;从vcA或vcB弹出则无异常
  • 问题与自定义VerticalButton强相关:移除xib中的该按钮后弹出正常,保留按钮时从vcC任意位置弹出都会触发崩溃
原因分析

核心问题是VerticalButton的代码引发了无限布局递归循环:

  1. layoutSubviews方法中调用了updateButtonSize,而后者直接修改了按钮的frame.size
  2. 修改frame会触发UIKit重新调用layoutSubviews,形成无限循环
  3. 从vcC弹出时,导航栈的视图层级调整会触发高频布局计算,这个循环会持续占用CPU和内存资源,最终导致内存耗尽崩溃;vcA/vcB因视图结构差异,未触发该高频布局链
解决方案

1. 修复无限布局循环

改用系统的intrinsicContentSize机制自动计算尺寸,避免手动修改frame触发循环:

class VerticalButton: UIButton {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureButton()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configureButton()
    }

    private func configureButton() {
        var configuration = UIButton.Configuration.plain()
        configuration.imagePlacement = .top
        configuration.imagePadding = 8
        configuration.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)

        self.configuration = configuration

        self.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        self.setContentHuggingPriority(.defaultHigh, for: .vertical)
        self.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
        self.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
    }

    // 重写intrinsicContentSize返回自定义计算的尺寸
    override var intrinsicContentSize: CGSize {
        guard let image = imageView?.image, let title = titleLabel?.text else {
            return super.intrinsicContentSize
        }

        let titleSize = (title as NSString).size(withAttributes: [.font: titleLabel?.font ?? UIFont.systemFont(ofSize: 17)])
        let imageSize = image.size

        let contentHeight = imageSize.height + titleSize.height + (configuration?.imagePadding ?? 0)
        let contentWidth = max(imageSize.width, titleSize.width)

        let width = contentWidth + (configuration?.contentInsets.leading ?? 0) + (configuration?.contentInsets.trailing ?? 0)
        let height = contentHeight + (configuration?.contentInsets.top ?? 0) + (configuration?.contentInsets.bottom ?? 0)
        
        return CGSize(width: width, height: height)
    }

    override func setTitle(_ title: String?, for state: UIControl.State) {
        super.setTitle(title, for: state)
        invalidateIntrinsicContentSize() // 触发系统重新计算尺寸
    }

    override func setImage(_ image: UIImage?, for state: UIControl.State) {
        super.setImage(image, for: state)
        invalidateIntrinsicContentSize() // 触发系统重新计算尺寸
    }
}

2. 额外优化

  • 移除awakeFromNib中的configureButton调用:init?(coder:)已经完成配置,避免重复执行
  • 永远不要在layoutSubviews中修改视图frame:这会破坏UIKit的自动布局流程,极易引发循环布局问题

内容的提问来源于stack exchange,提问作者Nguyễn Đức Thành

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:58:25