簡體   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