You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:59:58