[英]How can I manage the potential endless pushing of view controllers onto the navigation controller stack? iOS
[英]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是否在堆棧中,或者是從新鮮等加載。因為實際問題在於您的觀察者。
有許多正確的方法來管理視圖層次結構,但我將分享一種我發現簡單和情感的方法。
基本上,我在注銷時輸入了主UIWindow
的rootViewController
。 另外,我以編程方式提供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.