【问题标题】:How to correctly manage memory stack and view controllers?如何正确管理内存堆栈和视图控制器?
【发布时间】:2018-05-03 17:20:05
【问题描述】:

我真的在为这些基本的 iOS 编程问题苦苦挣扎,但我就是不知道发生了什么以及如何解决它。

我有我的主登录控制器,它检测用户何时登录并在验证成功时显示下一个控制器:

@interface LoginViewController (){

    //Main root instance
    RootViewController *mainPlatformRootControler;
}

-(void)loggedInActionWithToken:(NSString *)token anonymous:(BOOL)isAnon{
    NSLog(@"User loged in.");

    mainPlatformRootControler = [self.storyboard instantiateViewControllerWithIdentifier:@"rootViewCOntrollerStoryIdentifier"];

    [self presentViewController:mainPlatformRootControler animated:YES completion:^{

    }];

}

而且效果很好,没问题。

我的麻烦是处理注销。如何完全删除 RootViewController 实例并显示一个新实例?

我可以看到 RootViewController 实例正在堆叠,因为我有多个观察者,并且在注销然后登录后它们被多次调用(就像我退出和重新进入的次数一样)。

我尝试了以下方法但没有成功:

首先在 RootViewController 中检测注销并关闭:

[self dismissViewControllerAnimated:YES completion:^{
                [[NSNotificationCenter defaultCenter] postNotificationName:@"shouldLogOut" object:nil];

            }];

然后在 LoginViewController 中:

-(void)shouldLogOut:(NSNotification *) not{
    NSLog(@"No user signed in");
    mainPlatformRootControler = NULL;
    mainPlatformRootControler = nil;
}

那么我该如何处理呢?我知道它是一个基本的内存句柄,但我就是不知道怎么做?

【问题讨论】:

  • 为什么需要在LoginViewController 上保留mainPlatformRootControler?如果RootViewController 是从LoginViewController 提供的,您可以使用self.presentingViewControllerRootViewController 获取loginViewController,然后在不使用通知的情况下调用方法
  • “我可以看到 RootViewController 实例正在堆叠,因为我有多个观察者”这是错误的,通知中心不保留观察者..
  • 请提供您在导航堆栈中添加和“删除”RootViewController 的代码

标签: ios objective-c memory-management viewcontroller


【解决方案1】:

首先,您必须观察 viewDidLoad 中的“shouldLogOut”应该如下所示:

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(shouldLogout:) name:@"shouldLogout" object:nil];

然后在dismissViewControllerAnimated 中应该如下所示:

[self dismissViewControllerAnimated:true completion:^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"shouldLogOut" object:nil];
    }];

你需要在登录视图控制器中定义 shouldLogOut: 选择器

-(void)shouldLogOut:(NSNotification *) not{
    mainPlatformRootControler = nil;
}

希望对您有所帮助!

【讨论】:

    【解决方案2】:

    问题可能是您在注销时从未关闭RootViewController。通过将属性mainPlatformRootControler 设置为nil,您只是从LoginViewController 的角度放弃了对象的所有权。这并没有说明任何其他也拥有对 mainPlatformRootControler 背后的对象的引用。

    要解决此问题RootViewController 中添加一个通知观察者,用于注销 通知,当收到通知时,通过dismiss(animated:completion) 自行关闭

    奖励 如果您所做的只是将其保存以消除它,您也不需要属性mainPlatformRootControler。通过正确地关闭它(以我上面写的方式),它会自动被清理,因此也不需要担心nil将其删除。 (现在如果你有其他原因保留mainPlatformRootControler,那么不要明显删除它。

    【讨论】:

    • 安迪对不起,我完全忘了说我正在关闭它里面的 RootViewController。我已经更新了答案
    【解决方案3】:

    因为登录和注销是一次性的过程,所以登录后,不用显示新的控制器,只需将登录控制器替换为主控制器即可。

    让我们理解这一点: 您有带窗口的主应用程序委托。

    didFinishLaunch 中的代码:

    if (loggedIn) {
         self.window = yourMainController
    } else {
         self.window = loginController
    }
    

    LoginController 中的代码: LoginController 会有 AppDelegate 的实例,登录后需要修改

    appDelegate.window = mainController

    MainController 中的代码: MainController 将有 AppDelegate 的实例,注销后,您必须更改

    appDelegate.window = loginController

    希望对你有帮助!!

    【讨论】:

      【解决方案4】:

      你是否在LoginViewControllerviewDidLoad 中添加了通知观察者,如下所示

      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shouldLogOut:) name:@"shouldLogOut" object:nil];
      

      我猜你错过了这个,那么你的登录类在RootViewController被解雇后无法收到通知。

      【讨论】:

        【解决方案5】:

        正如你所说,有多个观察者创建问题,那么当你不需要它时,你必须删除你的观察者。

        在您的 RootViewController 中

        -(void)viewWillAppear:(BOOL)animated  
        {  
          [super viewWillAppear:animated];  
        
          // Add observer
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shouldLogout:) name:@"shouldLogout" object:nil];
        }
        
        - (void)viewWillDisappear:(BOOL)animated {
            [super viewWillDisappear:animated];
        
            // Remove observer by name
            [[NSNotificationCenter defaultCenter] removeObserver:self name:@"shouldLogout" object:nil];
        }
        

        因此,通过这种方式,您不必考虑您的 RootViewController 是否在堆栈中,或者它是从新加载的等等。因为实际问题出在您的观察者身上。

        【讨论】:

          【解决方案6】:

          管理视图层次结构的正确方法有很多,但我将分享一种我发现的简单有效的方法。

          基本上,我在注销/登录时换掉主要的UIWindowrootViewController。此外,我以编程方式提供rootViewController,而不是让@UIApplicationMain 加载初始视图控制器。这样做的好处是,在应用启动期间,如果用户已登录,则无需加载 Login.storyboard

          show 函数可以配置为适合您的风格,但我喜欢交叉溶解过渡,因为它们非常简单。

          import UIKit
          
          @UIApplicationMain
          class AppDelegate: UIResponder, UIApplicationDelegate {
          
              lazy var window: UIWindow? = {
          
                  let window = UIWindow()
                  window.makeKeyAndVisible()
          
                  return window
              }()
          
              func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
          
                  // Your own logic here
                  let isLoggedIn = false
          
                  if isLoggedIn {
                      show(MainViewController(), animated: false)
                  } else {
                      show(LoginViewController(), animated: false)
                  }
          
                  return true
              }
          }
          
          class LoginViewController: UIViewController {
          
              override func loadView() {
                  let view = UIView()
                  view.backgroundColor = .red
                  let logoutButton = UIButton()
                  logoutButton.setTitle("Log In", for: .normal)
                  logoutButton.addTarget(self, action: #selector(login), for: .touchUpInside)
                  view.addSubview(logoutButton)
                  logoutButton.translatesAutoresizingMaskIntoConstraints = false
          
                  NSLayoutConstraint.activate(
                      [logoutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                       logoutButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)]
                  )
          
                  self.view = view
              }
          
              @objc
              func login() {
                  AppDelegate.shared.show(MainViewController())
              }
          }
          
          class MainViewController: UIViewController {
          
              override func loadView() {
                  let view = UIView()
                  view.backgroundColor = .blue
                  let logoutButton = UIButton()
                  logoutButton.setTitle("Log Out", for: .normal)
                  logoutButton.addTarget(self, action: #selector(logout), for: .touchUpInside)
                  view.addSubview(logoutButton)
                  logoutButton.translatesAutoresizingMaskIntoConstraints = false
          
                  NSLayoutConstraint.activate(
                      [logoutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                       logoutButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                      ]
                  )
          
                  self.view = view
              }
          
              @objc
              func logout() {
                  AppDelegate.shared.show(LoginViewController())
              }
          }
          
          extension AppDelegate {
          
              static var shared: AppDelegate {
                  // swiftlint:disable force_cast
                  return UIApplication.shared.delegate as! AppDelegate
                  // swiftlint:enable force_cast
              }
          }
          
          private let kTransitionSemaphore = DispatchSemaphore(value: 1)
          
          extension AppDelegate {
          
              /// Animates changing the `rootViewController` of the main application.
              func show(_ viewController: UIViewController,
                        animated: Bool = true,
                        options: UIViewAnimationOptions = [.transitionCrossDissolve, .curveEaseInOut],
                        completion: (() -> Void)? = nil) {
          
                  guard let window = window else { return }
          
                  if animated == false {
                      window.rootViewController = viewController
                      return
                  }
          
                  DispatchQueue.global(qos: .userInitiated).async {
                      kTransitionSemaphore.wait()
          
                      DispatchQueue.main.async {
          
                          let duration = 0.35
          
                          let previousAreAnimationsEnabled = UIView.areAnimationsEnabled
                          UIView.setAnimationsEnabled(false)
          
                          UIView.transition(with: window, duration: duration, options: options, animations: {
                              self.window?.rootViewController = viewController
                          }, completion: { _ in
                              UIView.setAnimationsEnabled(previousAreAnimationsEnabled)
          
                              kTransitionSemaphore.signal()
                              completion?()
                          })
                      }
                  }
              }
          }
          

          这段代码是一个完整的例子,你可以新建一个项目,清空“主界面”字段,然后把这段代码放到appdelegate中。

          产生的过渡:

          【讨论】:

            【解决方案7】:

            由于您正在解除 RootViewController 并且您在注销后将引用归零但实例未释放,唯一的另一种可能性是其他东西正在保留对 RootViewController 的引用。您可能有一个保留周期。 如果两个对象彼此具有强引用,则会发生保留循环。并且因为一个对象在其所有强引用都被释放之前不能被释放,所以你有内存泄漏。

            保留周期的示例包括:

                RootViewController *root = [[RootViewController alloc] init];
                AnOtherViewController *another = [[AnOtherViewController alloc] init];
                //The two instances reference each other
                root.anotherInstance = another;
                another.rootInstance = root; 
            

            或者

                self.block = ^{
                            //self is captured strongly by the block
                            //and the block is captured strongly by the self instance
                            NSLog(@"%@", self);
                        };
            

            解决方案是对其中一个引用使用弱指针。因为弱指针是不保留其目标的指针。 例如

            @property(weak) RootViewController *anotherInstance;
            

            _typeof(self) __weak weakSelf = self
            self.block = ^{
                         _typeof(self) strongSelf = weakSelf
                        //self is captured strongly by the block
                        //and the block is captured strongly by the self instance
                        NSLog(@"%@", strongSelf);
                    };
            

            【讨论】:

              猜你喜欢
              • 2013-11-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-10-10
              • 2017-04-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多