从vcC弹出UIViewController时内存溢出崩溃问题求助
问题描述
- App导航结构:
UINavigationController -> vcA -> vcB -> vcC - 从vcC弹出任意UIViewController时,内存持续增长直至
out-of-memory崩溃;从vcA或vcB弹出则无异常 - 问题与自定义
VerticalButton强相关:移除xib中的该按钮后弹出正常,保留按钮时从vcC任意位置弹出都会触发崩溃
原因分析
核心问题是VerticalButton的代码引发了无限布局递归循环:
layoutSubviews方法中调用了updateButtonSize,而后者直接修改了按钮的frame.size- 修改frame会触发UIKit重新调用
layoutSubviews,形成无限循环 - 从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
相关产品推荐
相关产品推荐

