ActivityIndicator解包Optional值崩溃:代码规范与修复方案咨询
1. 代码格式与JSON POST请求的正确性检查
你的代码存在几处需要优化的细节,部分写法有崩溃风险或不符合iOS开发最佳实践:
函数调用中的强制解包问题
- Reachability调用里
(networkReachability?.currentReachabilityStatus())!.rawValue使用了强制解包!,如果networkReachability初始化失败为nil,这里会直接崩溃,建议改为可选绑定:if let networkReachability = Reachability.forInternetConnection(), let status = networkReachability.currentReachabilityStatus() { let networkStatus = status.rawValue // 后续逻辑 } else { SharedClass.sharedInstance.alertWindow(title: "", message: "无法检测网络状态") return } - URLRequest初始化
URL(string: url)!同样是强制解包,若url是无效字符串(比如空、格式错误),会触发崩溃,应该加校验:guard let requestUrl = URL(string: url) else { print("无效请求URL: \(url)") SharedClass.sharedInstance.stopActivityIndicator() SharedClass.sharedInstance.alertWindow(title: "", message: "请求地址异常") return } var request = URLRequest(url: requestUrl) - JSON解析中的
response!和res[0]["message"] as! String都是强制解包,一旦JSON结构不符合预期就会崩溃,需改为安全的类型转换:do { guard let response = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else { print("JSON结构解析失败") return } print(response) guard let status = response["status"] as? String else { print("无法获取请求状态") return } if status == "SUCCESS" { // 处理成功逻辑 } else { guard let resArray = response["res"] as? [[String: Any]], let message = resArray.first?["message"] as? String else { print("无法解析错误信息") return } SharedClass.sharedInstance.alertWindow(title: "", message: message) } } catch let error as NSError { print("JSON解析错误: \(error.localizedDescription)") }
POST请求参数拼接问题
你手动拼接参数let parameters = "imei=\(deviceID)&devid=\(deviceID)&ver=\(ver)"存在编码风险(比如参数包含特殊字符时会导致请求失败),推荐用URLComponents构建参数:
guard let requestUrl = URL(string: url) else { // 错误处理 return } var components = URLComponents(url: requestUrl, resolvingAgainstBaseURL: false)! components.queryItems = [ URLQueryItem(name: "imei", value: deviceID), URLQueryItem(name: "devid", value: deviceID), URLQueryItem(name: "ver", value: ver) ] request.httpBody = components.query?.data(using: .utf8)
2. 崩溃问题的解决方案
崩溃的直接原因是UIApplication.shared.keyWindow!强制解包了nil值。弹出alertWindow后,临时创建的alertWindow会成为当前keyWindow,当alert关闭后这个临时window会被释放,此时UIApplication.shared.keyWindow可能暂时为nil;同时iOS13+后keyWindow属性已被弃用,原获取方式不再可靠。此外SharedClass中Spinner和透明视图的生命周期管理也有漏洞。
修复步骤:
1. 安全获取当前Window(兼容iOS13+)
替换SharedClass中let window = UIApplication.shared.keyWindow!为以下安全逻辑:
guard let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .first(where: {$0.isKeyWindow}) else { print("无法获取当前活跃窗口") return }
这个方法会遍历当前活跃的场景,精准找到正在显示的keyWindow,避免强制解包。
2. 优化Spinner的生命周期管理
每次显示Spinner前,先移除已存在的视图,避免重复添加导致异常:
func activityIndicator(view:UIView) { DispatchQueue.main.async { // 先清理已存在的Spinner和透明视图 self.stopActivityIndicator() guard let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .first(where: {$0.isKeyWindow}) else { print("无法获取当前窗口") return } self.transparentView = UIView(frame: window.bounds) self.transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.4) window.addSubview(self.transparentView) // 根据设备类型初始化Spinner self.spinner = UIDevice.current.userInterfaceIdiom == .pad ? UIActivityIndicatorView(style: .whiteLarge) : UIActivityIndicatorView(style: .white) self.spinner.frame = CGRect(x: 0, y: 0, width: self.spinner.style == .whiteLarge ? 60 : 40, height: self.spinner.style == .whiteLarge ? 60 : 40) self.spinner.center = view.center self.transparentView.addSubview(self.spinner) self.spinner.startAnimating() // 超时自动停止 DispatchQueue.main.asyncAfter(deadline: .now() + 600.0) { self.stopActivityIndicator() } } }
3. 修复alertWindow实现(避免临时Window干扰)
当前alertWindow创建临时UIWindow的方式容易打乱keyWindow状态,推荐直接在当前顶层ViewController上弹出alert:
func alertWindow(title: String, message: String) { DispatchQueue.main.async { guard let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .first(where: {$0.isKeyWindow}), let rootVC = window.rootViewController else { print("无法获取顶层视图控制器") return } let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default) alert.addAction(okAction) // 找到当前最顶层的ViewController var topVC = rootVC while let presentedVC = topVC.presentedViewController { topVC = presentedVC } topVC.present(alert, animated: true) } }
4. 停止Spinner时的安全处理
在stopActivityIndicator中添加可选绑定,避免nil值访问:
func stopActivityIndicator() { DispatchQueue.main.async { self.spinner.stopAnimating() self.spinner.removeFromSuperview() self.transparentView?.removeFromSuperview() // 用?避免transparentView为nil时崩溃 // 重置变量,释放引用 self.transparentView = nil } }
通过以上修改,既能解决崩溃问题,也能大幅提升代码的健壮性和安全性。
内容的提问来源于stack exchange,提问作者Naresh

