即使在分配值后显示 nil 的弱可选变量?

Posted

技术标签:

【中文标题】即使在分配值后显示 nil 的弱可选变量?【英文标题】:weak optional variable showing nil even after assigning value? 【发布时间】:2021-12-29 09:57:24 【问题描述】:

我有一个 LoginCoordinator 类,它的 start() 方法实例化了 LoginViewController。但即使在从 LoginCoordinator 分配 LoginViewController 中的协调器变量之后,“有时”协调器变量在视图控制器中也是 nil。

“有时”是指,当我在 start() 方法中调试并保留断点时,会分配值。

这是我的登录协调员

class LoginCoordinator: Coordinator 
var navigationController: UINavigationController
weak var parent: MainCoordinator?
var childCoordinators: [Coordinator]?


init(navController: UINavigationController) 
    self.navigationController = navController


func start() 
    let vc: LoginViewController = UIStoryboard.main.instantiateViewController()
    vc.coordinator = self
    navigationController.pushViewController(vc,animated: true)


func goToSignUp() 
    let signUpCoordinator = SignUpCoordinator(navController: navigationController)
    childCoordinators?.append(signUpCoordinator)
    signUpCoordinator.start()


func goToHomeVC() 
    let homeCoordinator = HomeCoordinator(navControl: navigationController)
    childCoordinators?.append(homeCoordinator)
    homeCoordinator.start()
 

这是我的登录视图控制器

class LoginViewController: UIViewController 

@IBOutlet weak private var userName: HubTextField!
@IBOutlet weak private var password: HubTextField!
@IBOutlet weak private var forgotPassword: UILabel!
@IBOutlet weak var loginButton: HubButton!
var viewModel: LoginSignupVM = .init(totalItem: 2)
weak var coordinator: LoginCoordinator?

fileprivate func setupForgotPassword() 
    forgotPassword.isUserInteractionEnabled = true
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(forgotTapped))
    forgotPassword.addGestureRecognizer(tapGesture)


override func viewDidLoad() 
    super.viewDidLoad()
    loginButton.appearance = Appearance(cornerRadius: loginButton.frame.height/2)
    loginButton.setTitle(TitleText.loginText, for: .normal)
    loginButton.isEnabled = false
    setupTextField()
    hideKeyboardWhenTappedAround()
    setupForgotPassword()
    setupNavigationBar()
    
    // MARK: remove this later
    userName.text = "hirak@gmail.co"
    password.text = "asdfg123"


private func setupNavigationBar() 
    let titleView = UILabel(frame: .zero)
    titleView.text = "Login Up"
    titleView.font = UIFont.systemFont(ofSize: 20)
    navigationItem.titleView = titleView
    rightButton()


fileprivate func rightButton() 
    let rightButton = UIBarButtonItem(title: "signUp", style: .plain, target: self, action: #selector(goToSignUPScreen))
    rightButton.title = "sign Up"
    navigationItem.rightBarButtonItem = rightButton


@objc private func goToSignUPScreen() 
    coordinator?.goToSignUp()


@objc private func backButtonClicked() 
    navigationController?.popViewController(animated: true)


@objc private func forgotTapped() 
    //MARK: todo forgot password


private func assignTags() 
    userName.tag = 0
    password.tag = 1


private func setupTextField() 
    userName.placeHolder = TitleText.email
    userName.textFieldType = .email
    userName.delegate = self
    password.placeHolder = TitleText.password
    password.textFieldType = .password
    password.delegate = self
    assignTags()


private func goToHomeViewController() 
    coordinator?.goToHomeVC()


@IBAction func onLoginClick(_ sender: Any) 
    //MARK: todo login click change hardcoded verification
    if let email = userName.text, let password = password.text 
        if email == "hirak@gmail.co" && password == "asdfg123" 
            goToHomeViewController()
        
    
  


extension LoginViewController: ValidatorButton 
func addNewValidated(tag: Int, result: ValidationResult) 
    viewModel.validationList[tag] = result
    loginButton.isEnabled = viewModel.loginButtonEnabled()
   
  

LoginCoordinator 从 MainCoordinator 调用为:

class MainCoordinator: Coordinator 
var isLogin: Bool = false
var navigationController: UINavigationController
var childCoordinators: [Coordinator]? = []
let window: UIWindow?

init(navController: UINavigationController, window: UIWindow) 
    self.navigationController = navController
    self.window = window


func start() 
    if isLogin 
       let child = HomeCoordinator(navControl: navigationController)
        childCoordinators?.append(child)
        child.start()
     else 
    let child = LoginCoordinator(navController: 
navigationController)
        childCoordinators?.append(child)
        child.start()
    
    

并且从sceneDelegate调用MainCoordinator如下:

   func scene(_ scene: UIScene, willConnectTo session: 
UISceneSession, options connectionOptions: 
UIScene.ConnectionOptions) 
    guard let scene = (scene as? UIWindowScene) else  return 
    let window = UIWindow(windowScene: scene)
    self.window = window
    
    let navigationController = UINavigationController()
    let mainCoordinator = MainCoordinator(navController: 
  navigationController,window: window)
    mainCoordinator.start()
    window.rootViewController = navigationController
    window.makeKeyAndVisible()
  

【问题讨论】:

在 DispatchQueue.main.async 块中赋值 谁对协调员有强烈的引用?某些东西需要对协调器有很强的引用才能不被释放。 @Sweeper 有一个不同的协调员持有对 LoginCoordinator 的强烈引用。 @GopiDonga 为什么会这样? 啊哈,我就知道了!您将mainCoordinator 声明为局部变量,在声明它的方法完成后,它将消失 【参考方案1】:

mainCoordinatorwillConnectTo 委托方法中被声明为局部变量:

let mainCoordinator = MainCoordinator(navController: 
    navigationController,window: window)

这意味着它可以在willConnectTo 中最后一次访问它之后被释放。

由于mainCoordinator 拥有对LoginCoordinator 的唯一强引用,它也将被释放。这就是您在视图控制器中看到 nil 的原因 - 此时,mainCoordinator 已被释放。

您仍然在LoginCoordinator.start 中看到一个非零值,因为此时mainCoordinator.start 还没有完成,所以mainCoordinator 还没有被释放。

要解决这个问题,您可以通过声明一个名为 mainCoordinator 的属性来使场景委托持有对主协调器的强引用:

var mainCoordinator: MainCoordinator!

// in willConnectTo:
mainCoordinator = MainCoordinator(navController: 
    navigationController,window: window)

【讨论】:

实际上mainCoordinator 甚至可以在willConnectTo 完成运行之前释放。 Swift 目前不保证对象在其周围范围内的生命周期。 @Sulthan 哦,我不知道!已编辑。现在措辞好点了吗? @Sweeper 非常感谢,这工作正常,我从没想过问题会这么深:D。 @Sweeper 这只是吹毛求疵 :) 我认为这很快就会改变。我记得在论坛上看到过一个提案。

以上是关于即使在分配值后显示 nil 的弱可选变量?的主要内容,如果未能解决你的问题,请参考以下文章

从 UITableView 更改 viewController 时将可选值显示为“nil”

发现可选值错误 nil swift 3

为啥 UITextField.text 是可选的?

即使在指针设置为 nil 后 UIMenuItem 仍会继续显示

自定义容器:@IBOutlets nil 即使视图显示正确

UITextField 总是返回 nil