即使在分配值后显示 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】:
mainCoordinator
在willConnectTo
委托方法中被声明为局部变量:
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”