Ios Swift:以MODALY方式呈现并解除导航控制器

Ios Swift:以MODALY方式呈现并解除导航控制器,ios,swift,uinavigationcontroller,dismissviewcontroller,Ios,Swift,Uinavigationcontroller,Dismissviewcontroller,我有一个非常常见的iOS应用程序场景: 应用程序的MainVC是一个UIDABBarController。我在AppDelegate.swift文件中将此VC设置为rootViewController: func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

我有一个非常常见的iOS应用程序场景:

应用程序的MainVC是一个UIDABBarController。我在AppDelegate.swift文件中将此VC设置为rootViewController:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    window = UIWindow()
    window?.rootViewController = MainVC()
    window?.makeKeyAndVisible()
}
当用户注销时,我会提供一个导航控制器,其中LandingVC作为导航堆栈的根视图控制器

let navController = UINavigationController(rootViewController: LandingVC)
self.present(navController, animated: true, completion: nil)
在登陆VC内部,点击Login按钮,然后将LoginVC推到堆栈顶部

navigationController?.pushViewController(LoginVC(), animated: true)
当用户成功登录时,我将从LoginVC内部关闭导航控制器

self.navigationController?.dismiss(animated: true, completion: nil)
基本上,我试图实现以下流程:

一切正常,但问题是LoginVC从未从内存中释放。因此,如果一个用户登录和注销4次(没有理由这么做,但仍然有机会),我将在内存中看到LoginVC4次,登陆vc 0次

我不明白为什么LoginVC没有被解除分配,而LandingVC却被解除分配

在我看来(并纠正我的错误),因为导航控制器是显示的,并且它包含两个VCs(LandingVCLoginVC),当我在LoginVC中使用disclose()时,它应该关闭导航控制器,因此两个VCs都包含

  • MainVC:演示VC
  • 导航控制器:显示VC
来自苹果文档:

呈现视图控制器负责解除其呈现的视图控制器。如果在呈现视图控制器本身上调用此方法,UIKit将要求呈现视图控制器处理该问题

当我在LoginVC中关闭导航控制器时,我认为出现了问题。有没有一种方法可以在用户登录后立即在MainVC(演示VC)中触发dismise()

PS:使用下面的代码不会起作用,因为它会弹出到导航堆栈的根视图控制器,即LandingVC;而不是对我

self.navigationController?.popToRootViewController(animated: true)
任何帮助都将不胜感激

====================================

我的登录代码:


经过大量搜索,我想我找到了解决方案:

激发我灵感的是所有人对这个问题和这篇文章的评论:

我将从我的编码哲学开始:我喜欢保持我的代码分离和干净。因此,我总是尝试创建一个包含我想要的所有元素的UIView,然后将其“链接”到适当的视图控制器。但是当UIView有按钮,并且这些按钮需要完成操作时会发生什么呢?众所周知,视图中没有“逻辑”的空间:

class LoginView: UIView {

    // connect to view controller
    var loginAction: (() -> Void)?
    var forgotPasswordAction: (() -> Void)?

    // some code that initializes the view, creates the UI elements and constrains them as well

    // let's see the button that will login the user if credentials are correct
    let loginButton: UIButton = {
        let button = UIButton(title: "Login", font: UIFont.FontBook.AvertaSemibold.of(size: 20), textColor: .white, cornerRadius: 5)
        button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
        button.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
        return button
    }()

    // button actions
    @objc func handleLogin() {
        loginAction?()
    }

    @objc func handleForgotPassword() {
        forgotPasswordAction?()
    }
}
因此,正如文章所说:

LoginVC强烈引用了LoginView,它强烈引用了刚刚创建了对self的强烈引用的loginAction放弃密码操作的闭包

你可以很清楚地看到,我们有一个循环。这意味着,如果退出此视图控制器,则无法将其从内存中删除,因为它仍然被闭包引用

这可能就是为什么我的LoginVC从未从内存中释放的原因。[剧透警报:这就是原因!]

如问题所示,LoginVC负责执行所有按钮操作。我之前所做的是:

class LoginVC: UIViewController {

    // reference LoginView
    var loginView: LoginView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }

    fileprivate func setupView() {
        let mainView = LoginView(frame: self.view.frame)
        self.loginView = mainView
        self.view.addSubview(loginView)

        // link button actions from LoginView to functionality inside LoginVC

        // THIS IS WHAT IS CAUSING THE RETAIN CYCLE <--------------------
        self.loginView.loginAction = loginButtonClicked
        self.loginView.forgotPasswordAction = forgotPasswordButtonClicked

        // pin view
        .....
    }

    // our methods for executing the actions
    fileprivate func loginButtonClicked() { ... }
    fileprivate func forgotPasswordButtonClicked() { ... }

}
在这个简单的代码更改之后(是的,我花了10天的时间才弄明白),一切都像以前一样运行,但是内存感谢我

有几件事要说:

  • 当我第一次注意到这个内存问题时,我责备自己没有正确地关闭/弹出视图控制器。您可以在我前面的问题中找到更多信息:

  • 在这个过程中,我学到了很多关于显示/推送视图控制器和导航控制器的知识;因此,尽管我看错了方向,但我确实学到了很多

  • 没有什么是免费的,记忆泄露教会了我这一点


  • 希望我能在同样的问题上帮助别人

    在MainVC中调用Denit怎么样?你是如何得出LoginVC泄漏的结论的?如果仍然有一个强有力的参考文件保存在你的
    landing/login
    vc中,就会发生这种情况。它可以是回调、委托,甚至是属性。只要确保你也把这些东西卖掉就行了。完成后将它们设置为nil,您应该能够看到Denit被调用。请不要直接打电话给我deinit@AleksandrMedvedev如果您在xCode的“调试导航器”中使用“查看内存图层次结构”,您可以看到它。您的LoginVC中可能有一些强引用。你可能想在这里发布你的VC代码。
    class LoginVC: UIViewController {
    
        // reference LoginView
        var loginView: LoginView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            setupView()
        }
    
        fileprivate func setupView() {
            let mainView = LoginView(frame: self.view.frame)
            self.loginView = mainView
            self.view.addSubview(loginView)
    
            // link button actions from LoginView to functionality inside LoginVC
    
            // THIS IS WHAT IS CAUSING THE RETAIN CYCLE <--------------------
            self.loginView.loginAction = loginButtonClicked
            self.loginView.forgotPasswordAction = forgotPasswordButtonClicked
    
            // pin view
            .....
        }
    
        // our methods for executing the actions
        fileprivate func loginButtonClicked() { ... }
        fileprivate func forgotPasswordButtonClicked() { ... }
    
    }
    
    fileprivate func setupView() {
    
        ...
        ...
        ...
    
        self.loginView.loginAction = { [unowned self] in
            self.loginButtonClicked()
        }
    
        self.loginView.forgotPasswordAction = { [unowned self] in
            self.forgotPasswordButtonClicked()
        }
    
        self.loginView.textInputChangedAction = { [unowned self] in
            self.textInputChanged()
        }
    }