iOS:从闭包调用UIAlertController时重复弹窗及崩溃问题咨询
这个问题我之前也碰到过,本质就是重复尝试在同一个视图控制器上弹出警告框导致的,咱们一步步拆解问题和解决办法:
问题根源分析
你看到的警告Attempt to present <UIAlertController: 0x7ff8fd0b5800> on <LoginViewController: 0x7ff8fcc0deb0> which is already presenting <UIAlertController: 0x7ff8fe0cca00>,核心原因有两个:
- 登录请求被多次触发(比如用户快速点击登录按钮多次),导致回调闭包重复执行
self.displayFailedLoginAlert(); - 当第一个警告框还未关闭时,第二个警告框的
present请求就已发起,而系统不允许同一个VC同时present多个控制器,因此抛出警告。
另外你的displayFailedLoginAlert里还有个小冗余:点击"Dismiss"按钮时手动调用alert.dismiss完全没必要——UIKit中,UIAlertAction默认会在点击后自动关闭对应的警告框,这段代码纯属画蛇添足。
解决方案
1. 从源头阻止重复登录请求
首先要确保同一时间只有一个登录请求在进行,避免多次触发回调。假设你的登录按钮点击事件是loginButtonTapped,修改逻辑如下:
@IBAction func loginButtonTapped(_ sender: UIButton) { // 检查是否正在登录,避免重复发起请求 guard !loggingIn else { return } loggingIn = true _ = UserProfile.logIn(emailAddressLabel.text!, passwordLabel.text!) { success in if success { self.performSegue(withIdentifier: "mainTabBarSegue", sender: nil) } else { self.displayFailedLoginAlert() } // 确保在主线程更新UI状态(如果logIn回调不在主线程) DispatchQueue.main.async { self.loggingIn = false } } }
2. 确保每次只弹出一个警告框
修改displayFailedLoginAlert函数,在弹出前先检查当前VC是否已经在present其他控制器,如果是则跳过或处理现有警告:
func displayFailedLoginAlert() { // 若已有弹出的控制器,直接返回(也可根据需求更新现有警告内容) guard presentedViewController == nil else { return } let alert = UIAlertController(title: "Login", message: "Login Failed", preferredStyle: .alert) // 移除手动dismiss的代码,UIAlertAction默认会关闭警告 alert.addAction(UIAlertAction(title: "Dismiss", style: .default)) // 确保在主线程弹出警告(如果回调不在主线程) DispatchQueue.main.async { self.present(alert, animated: true, completion: nil) } }
这里把animated设为true是更符合iOS交互规范的,如果你需要无动画效果也可以保持false。
3. 为什么把UIAlertController设为类成员会崩溃?
大概率是因为强引用循环:当你把alert设为类成员,同时alert的action handler里引用了self,就会形成self -> alert -> handler -> self的循环引用,导致内存无法释放,甚至在alert被dismiss后访问它时出现野指针崩溃。如果一定要用类成员管理alert,需要使用弱引用:
// 类成员 var loginAlert: UIAlertController? func displayFailedLoginAlert() { if let existingAlert = loginAlert, existingAlert.isBeingPresented { return } let alert = UIAlertController(title: "Login", message: "Login Failed", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .default) { [weak self] _ in self?.loginAlert = nil }) loginAlert = alert DispatchQueue.main.async { self.present(alert, animated: true) } }
不过这种方式没必要,前面的两种方案已经足够解决问题。
内容的提问来源于stack exchange,提问作者Brian Parkes

