【发布时间】:2018-12-05 16:53:02
【问题描述】:
我有一个很常见的 iOS 应用场景:
应用的MainVC是一个UITabBarController。我在 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)
在 LandingVC 中单击 Login 按钮,LoginVC 被推到堆栈顶部。
navigationController?.pushViewController(LoginVC(), animated: true)
当用户成功登录时,我从 LoginVC 内部解除导航控制器。
self.navigationController?.dismiss(animated: true, completion: nil)
基本上,我正在尝试实现以下流程:
一切正常,但问题是 LoginVC 从未从内存中释放。因此,如果用户登录和注销 4 次(没有理由这样做,但仍有机会),我将在内存中看到 LoginVC 4 次和 LandingVC 0 次。
我不明白为什么 LoginVC 没有被释放,而 LandingVC 却被释放了。
在我的脑海中(并纠正我的错误),因为当我使用导航控制器时,它包含 2 个 VC(LandingVC 和 LoginVC) LoginVC 中的 dismiss() 它应该关闭导航控制器,因此两者都包含 VC。
- MainVC:展示VC
- 导航控制器:展示 VC
来自 Apple 文档:
呈现视图控制器负责关闭它呈现的视图控制器。如果您在呈现的视图控制器本身上调用此方法,UIKit 会要求呈现的视图控制器处理解除。
我认为当我在 LoginVC 中关闭导航控制器时出现了问题。有没有办法在用户登录后立即在 MainVC(呈现 VC)中触发dismiss()?
PS:使用下面的代码不会成功,因为它会弹出到导航堆栈的根视图控制器,即 LandingVC;而不是 MainVC。
self.navigationController?.popToRootViewController(animated: true)
任何帮助将不胜感激!
======================================
我的 LoginVC 代码:
import UIKit
import Firebase
import NotificationBannerSwift
class LoginVC: UIViewController {
// reference LoginView
var loginView: LoginView!
override func viewDidLoad() {
super.viewDidLoad()
// dismiss keyboard when clicking outside textfields
self.hideKeyboard()
// setup view elements
setupView()
setupNavigationBar()
}
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 LoginViewController
self.loginView.loginAction = loginButtonClicked
self.loginView.forgotPasswordAction = forgotPasswordButtonClicked
self.loginView.textInputChangedAction = textInputChanged
// pin view
loginView.translatesAutoresizingMaskIntoConstraints = false
loginView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
loginView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
loginView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
fileprivate func setupNavigationBar() {
// make navigation controller transparent
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
// change color of text
self.navigationController?.navigationBar.tintColor = UIColor.white
// add title
navigationItem.title = "Login"
// change title font attributes
let textAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.white,
NSAttributedStringKey.font: UIFont.FontBook.AvertaRegular.of(size: 22)]
self.navigationController?.navigationBar.titleTextAttributes = textAttributes
}
fileprivate func loginButtonClicked() {
// some local authentication checks
// ready to login user if credentials match the one in database
Auth.auth().signIn(withEmail: emailValue, password: passwordValue) { (data, error) in
// check for errors
if let error = error {
// display appropriate error and stop rest code execution
self.handleFirebaseError(error, language: .English)
return
}
// if no errors during sign in show MainTabBarController
guard let mainTabBarController = UIApplication.shared.keyWindow?.rootViewController as? MainTabBarController else { return }
mainTabBarController.setupViewControllers()
// this is where i dismiss navigation controller and the MainVC is displayed
self.navigationController?.dismiss(animated: true, completion: nil)
}
}
fileprivate func forgotPasswordButtonClicked() {
let forgotPasswordViewController = ForgotPasswordViewController()
// present as modal
self.present(forgotPasswordViewController, animated: true, completion: nil)
}
// tracks whether form is completed or not
// disable registration button if textfields not filled
fileprivate func textInputChanged() {
// check if any of the form fields is empty
let isFormEmpty = loginView.emailTextField.text?.count ?? 0 == 0 ||
loginView.passwordTextField.text?.count ?? 0 == 0
if isFormEmpty {
loginView.loginButton.isEnabled = false
loginView.loginButton.backgroundColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 0.6)
} else {
loginView.loginButton.isEnabled = true
loginView.loginButton.backgroundColor = UIColor(red: 32/255, green: 215/255, blue: 136/255, alpha: 1.0)
}
}
}
【问题讨论】:
-
在MainVC中调用deinit怎么样?
-
您是如何得出 LoginVC 泄露的结论的?
-
好吧,如果您的
landing/loginvc 仍然有强大的参考资料,就会发生这种情况。它可以是回调、委托甚至是属性。只要确保你也取消分配这些。完成后将它们设置为 nil,您应该能够看到 deinit 被调用。并且请不要明确调用 deinit -
@AleksandrMedvedev 你可以在xCode的“Debug navigator”中使用“View memory graph hierarhcy”看到它。
-
您的 LoginVC 中可能有一些强引用。您可能想在此处发布您的 VC 代码。
标签: ios swift uinavigationcontroller dismissviewcontroller