Swift 5视图控制器切换引发内存泄漏问题求助
视图控制器切换时的内存泄漏问题
我的应用哪怕在最基础设置下,仅在视图控制器间切换也会缓慢泄漏内存。我用CATransition实现了VC的淡入淡出切换效果。
反复在IntroVC和FirstVC之间切换时,内存占用会持续攀升——速度很慢,大概每20次往返涨0.1MB,但确实一直在增加,说明有资源没被释放。如果加上背景、图片等元素,每次重新加载重复VC时,内存会跳涨约0.2MB。
我觉得不太可能是push/pop本身的问题,更大概率是我的代码结构有问题。试过用Instruments找泄漏点,但看不懂输出。求解决思路。
AppDelegate代码
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var coordinator: MainCoordinator? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { setupCore() return true } //MARK: - Setup methods private func setupCore() { let navController = UINavigationController() coordinator = MainCoordinator(navigationController: navController) coordinator?.start() window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = navController window?.makeKeyAndVisible() } }
MainCoordinator代码
import UIKit class MainCoordinator: NSObject, Coordinator, UINavigationControllerDelegate { var childCoordinators = [Coordinator]() var navigationController: UINavigationController var rootIntro: IntroVC! init(navigationController: UINavigationController) { self.navigationController = navigationController self.navigationController.setNavigationBarHidden(true, animated: false) } func start() { navigationController.delegate = self rootIntro = IntroVC.instantiate() rootIntro.coordinator = self navigationController.fadeTo(rootIntro) } //MARK: - 从Intro加载VC func showFirst() { let vc = FirstVC.instantiate() vc.coordinator = self navigationController.fadeTo(vc) } // MARK: - 返回上级VC func returnToRoot() { navigationController.fadeToRoot() } }
Coordinator协议代码
protocol Coordinator: AnyObject { var childCoordinators: [Coordinator] { get set } var navigationController: UINavigationController { get set } func start() } // MARK: - Storyboard protocol Storyboarded { static func instantiate() -> Self } extension Storyboarded where Self: UIViewController { static func instantiate() -> Self { let id = String(describing: self) let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main) return storyboard.instantiateViewController(withIdentifier: id) as! Self } }
UINavigationController扩展代码
extension UINavigationController { func fadeTo(_ toVC: UIViewController) { let transition = CATransition().fadeTransition() view.layer.add(transition, forKey: nil) pushViewController(toVC, animated: false) } func fadeToRoot() { let transition = CATransition().fadeTransition() view.layer.add(transition, forKey: nil) popToRootViewController(animated: false) } } extension CATransition { func fadeTransition() -> CATransition { let transition = CATransition() transition.duration = 0.3 transition.type = CATransitionType.fade return transition } }
IntroVC代码
class IntroVC: UIViewController, Storyboarded { weak var coordinator: MainCoordinator? override func viewDidLoad() { super.viewDidLoad() // 加载视图后进行额外设置 let title = UILabel(frame: CGRect(origin: CGPoint(x: 0, y: 100), size: CGSize(width: view.frame.width, height: 80))) title.text = "Intro VC" title.font = UIFont.systemFont(ofSize: 30, weight: .heavy) title.textAlignment = .center title.textColor = .lightGray view.addSubview(title) let button = UIButton(frame: CGRect(origin: CGPoint(x: (view.frame.width - 150)/2, y: (view.frame.height - 50)/2), size: CGSize(width: 150, height: 50))) button.backgroundColor = .green button.setTitleColor(.black, for: .normal) button.setTitle("-> First", for: .normal) button.addTarget(self, action: #selector(tapped), for: .touchUpInside) view.addSubview(button) } @objc func tapped(_ button: UIButton) { coordinator?.showFirst() } }
FirstVC代码
class FirstVC: UIViewController, Storyboarded { weak var coordinator: MainCoordinator? override func viewDidLoad() { super.viewDidLoad() // 加载视图后进行额外设置 let title = UILabel(frame: CGRect(origin: CGPoint(x: 0, y: 100), size: CGSize(width: view.frame.width, height: 80))) title.text = "First VC" title.font = UIFont.systemFont(ofSize: 30, weight: .heavy) title.textAlignment = .center title.textColor = .white view.addSubview(title) let button = UIButton(frame: CGRect(origin: CGPoint(x: (view.frame.width - 150)/2, y: (view.frame.height - 50)/2), size: CGSize(width: 150, height: 50))) button.backgroundColor = .red button.setTitleColor(.black, for: .normal) button.setTitle("<- Intro", for: .normal) button.addTarget(self, action: #selector(tapped), for: .touchUpInside) view.addSubview(button) } @objc func tapped(_ button: UIButton) { coordinator?.returnToRoot() } }
问题排查与修复思路
1. CATransition动画未清理,导致内存累积
每次调用fadeTo或fadeToRoot时,你都给导航控制器的view.layer添加了新的CATransition,但没有在动画结束后移除旧动画,也没设置唯一key让新动画覆盖旧的。这些动画对象会一直附着在图层上,持续占用内存。
修复代码:
extension UINavigationController { func fadeTo(_ toVC: UIViewController) { // 移除之前的过渡动画,或用唯一key覆盖 view.layer.removeAnimation(forKey: "fadeTransition") let transition = CATransition().fadeTransition() view.layer.add(transition, forKey: "fadeTransition") pushViewController(toVC, animated: false) } func fadeToRoot() { view.layer.removeAnimation(forKey: "fadeTransition") let transition = CATransition().fadeTransition() view.layer.add(transition, forKey: "fadeTransition") popToRootViewController(animated: false) } }
2. 确认视图控制器的生命周期与子视图创建
当前VC的viewDidLoad只会在初始化时调用一次,pop返回时不会重复创建子视图,这部分没问题。但如果后续在viewWillAppear等重复调用的方法里创建视图,会导致子视图重复添加,需避免这种情况。
3. Instruments工具使用技巧
如果还是找不到泄漏点,用Instruments的Allocations工具:
- 启动Allocations,切换VC几次后点击「Mark Generation」标记内存快照
- 对比前后快照,查看哪些对象数量持续增加
- 重点关注UIViewController实例、CALayer、CATransition这类对象的变化
内容的提问来源于stack exchange,提问作者GBasher
相关产品推荐
相关产品推荐

