[英]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.