繁体   English   中英

如何正确管理内存堆栈和视图控制器?

[英]How to correctly manage memory stack and view controllers?

我真的很挣扎这个基本的iOS编程的东西,但我无法弄清楚发生了什么以及如何解决它。

我有我的主Login控制器,用于检测用户何时登录,如果auth成功,则显示下一个控制器:

@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;
}

那我怎么办呢? 我知道它是一个基本的内存处理器,但我只是不知道如何?

首先,您必须在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:selector

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

希望这个能对您有所帮助!

问题很可能是你在注销发生时从不解雇RootViewController 通过将属性mainPlatformRootControler设置为nil ,您只需从LoginViewController的角度放弃对象的所有权。 这没有说明任何其他也拥有对mainPlatformRootControler背后的对象的引用。

为了解决这个问题,RootViewController添加一个通知观察器来注销通知,当收到通知观察者时,通过dismiss(animated:completion)解析自己dismiss(animated:completion)

奖金你也不需要属性mainPlatformRootControler如果你正在做的就是将它保存为零。 通过适当地解除它(以我上面写的方式),它将自动被清理,因此也不必担心nil (现在,如果你有其他理由保持mainPlatformRootControler ,那么显然不要删除它)。

由于登录和注销是一次性过程,因此登录后,只需将登录控制器替换为主控制器,而不是呈现新的控制器。

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

didFinishLaunch中的代码:

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

LoginController中的代码:LoginController将具有AppDelegate的实例,并且在登录后,您必须更改

appDelegate.window = mainController

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

appDelegate.window = loginController

我希望这有帮助 !!

您是否在LoginViewController viewDidLoad中添加了Notification observer,如下所示

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

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

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

在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是否在堆栈中,或者是从新鲜等加载。因为实际问题在于您的观察者。

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

基本上,我在注销时输入了主UIWindowrootViewController 另外,我以编程方式提供rootViewController而不是让@UIApplicationMain加载初始视图控制器。 这样做的好处是,在应用程序启动期间,如果用户已登录,则Login.storyboard加载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?()
                })
            }
        }
    }
}

此代码是一个完整的示例,您可以创建一个新项目,清除“主界面”字段,然后将此代码放入应用程序委托中。

由此产生的转变:

在此输入图像描述

由于你正在解雇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);
        };

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM