实现iOS应用全页面离线模式功能的技术求助
全局离线状态处理方案修复
问题分析
你在AppDelegate里的实现存在几个核心问题:
- 视图层级错误:直接用
addSubview(offlineVC.view)把视图加到window上,没有触发OfflineViewController的完整生命周期,也不符合iOS控制器的层级管理逻辑,导致功能失效。 - iOS 13+场景适配缺失:iOS 13及以后,窗口由
UIScene管理,AppDelegate里的window可能为空,直接通过appDelegate?.window无法获取正确的窗口实例。 - 线程安全问题:Reachability的回调在后台线程执行,直接操作UI会引发异常,必须切换到主线程处理。
- 重复监听问题:
OfflineViewController里再次启动Reachability监听,会导致重复回调,逻辑混乱。
解决方案(无第三方库)
我们通过单例网络管理器统一监听网络状态,再通过获取当前顶层ViewController的方式弹出离线页面,同时适配iOS 13+的场景架构。
步骤1:创建全局网络监听单例
直接基于系统SystemConfiguration框架实现,完全不需要第三方库:
import UIKit import SystemConfiguration // 网络状态枚举 enum NetworkStatus { case reachable case unreachable } class NetworkManager: NSObject { static let shared = NetworkManager() private var reachability: SCNetworkReachability? private var currentStatus: NetworkStatus = .reachable private override init() { super.init() setupReachability() } // 初始化网络监听 private func setupReachability() { var zeroAddress = sockaddr_in() zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) guard let reachability = withUnsafePointer(to: &zeroAddress, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { SCNetworkReachabilityCreateWithAddress(nil, $0) } }) else { return } self.reachability = reachability var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = Unmanaged.passUnretained(self).toOpaque() // 设置状态变化回调 SCNetworkReachabilitySetCallback(reachability, { _, flags, info in guard let info = info else { return } let manager = Unmanaged<NetworkManager>.fromOpaque(info).takeUnretainedValue() manager.updateStatus(with: flags) }, &context) // 将监听加入主运行循环 SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.commonModes.rawValue) } // 更新网络状态并发送通知 private func updateStatus(with flags: SCNetworkReachabilityFlags) { let isReachable = flags.contains(.reachable) let needsConnection = flags.contains(.connectionRequired) let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) let canConnectWithoutUser = canConnectAutomatically && !flags.contains(.interventionRequired) let newStatus: NetworkStatus = (isReachable && (!needsConnection || canConnectWithoutUser)) ? .reachable : .unreachable if newStatus != currentStatus { currentStatus = newStatus NotificationCenter.default.post(name: NSNotification.Name("NetworkStatusChanged"), object: currentStatus) } } // 获取当前网络状态 func getCurrentStatus() -> NetworkStatus { return currentStatus } }
步骤2:全局监听网络状态并弹出离线页面
iOS13+推荐:SceneDelegate实现
import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let _ = (scene as? UIWindowScene) else { return } // 注册网络状态变化通知 NotificationCenter.default.addObserver(self, selector: #selector(networkStatusChanged(_:)), name: NSNotification.Name("NetworkStatusChanged"), object: nil) // 初始化网络管理器 _ = NetworkManager.shared } @objc private func networkStatusChanged(_ notification: Notification) { guard let status = notification.object as? NetworkStatus else { return } // 切到主线程操作UI DispatchQueue.main.async { if status == .unreachable { // 避免重复弹出离线页面 if self.topViewController() is OfflineViewController { return } let storyboard = UIStoryboard(name: "Main", bundle: nil) let offlineVC = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController offlineVC.modalPresentationStyle = .fullScreen self.topViewController()?.present(offlineVC, animated: true) } } } // 获取当前顶层ViewController private func topViewController() -> UIViewController? { guard let rootVC = window?.rootViewController else { return nil } return getTopVC(from: rootVC) } private func getTopVC(from vc: UIViewController) -> UIViewController? { if let navVC = vc as? UINavigationController { return getTopVC(from: navVC.visibleViewController ?? navVC) } else if let tabVC = vc as? UITabBarController { return getTopVC(from: tabVC.selectedViewController ?? tabVC) } else if let presentedVC = vc.presentedViewController { return getTopVC(from: presentedVC) } else { return vc } } }
iOS12及以下:AppDelegate适配
如果需要支持iOS12及以下,在AppDelegate中添加如下逻辑:
import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 注册网络状态变化通知 NotificationCenter.default.addObserver(self, selector: #selector(networkStatusChanged(_:)), name: NSNotification.Name("NetworkStatusChanged"), object: nil) // 初始化网络管理器 _ = NetworkManager.shared return true } @objc private func networkStatusChanged(_ notification: Notification) { guard let status = notification.object as? NetworkStatus else { return } DispatchQueue.main.async { if status == .unreachable { if self.topViewController() is OfflineViewController { return } let storyboard = UIStoryboard(name: "Main", bundle: nil) let offlineVC = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController offlineVC.modalPresentationStyle = .fullScreen self.topViewController()?.present(offlineVC, animated: true) } } } private func topViewController() -> UIViewController? { guard let rootVC = window?.rootViewController else { return nil } return getTopVC(from: rootVC) } private func getTopVC(from vc: UIViewController) -> UIViewController? { if let navVC = vc as? UINavigationController { return getTopVC(from: navVC.visibleViewController ?? navVC) } else if let tabVC = vc as? UITabBarController { return getTopVC(from: tabVC.selectedViewController ?? tabVC) } else if let presentedVC = vc.presentedViewController { return getTopVC(from: presentedVC) } else { return vc } } // 其余SceneSession生命周期代码保留 }
步骤3:修改OfflineViewController
去掉重复的Reachability监听,改用通知或直接检查网络状态:
import UIKit class OfflineViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 监听网络恢复通知,自动关闭页面 NotificationCenter.default.addObserver(self, selector: #selector(onNetworkReachable), name: NSNotification.Name("NetworkStatusChanged"), object: nil) } @IBAction func tapTryAgain(_ sender: Any) { checkNetworkAndDismiss() } @objc private func onNetworkReachable(_ notification: Notification) { guard let status = notification.object as? NetworkStatus else { return } if status == .reachable { dismiss(animated: true) } } private func checkNetworkAndDismiss() { if NetworkManager.shared.getCurrentStatus() == .reachable { dismiss(animated: true) } else { // 提示用户仍无网络 let alert = UIAlertController(title: "提示", message: "当前仍无网络连接", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) } } deinit { NotificationCenter.default.removeObserver(self) } }
步骤4:清理原代码
删除首页ViewController中所有Reachability相关代码,现在由全局管理器统一处理。
关键注意事项
- 避免重复弹出:弹出离线页面前检查当前顶层VC是否已经是OfflineViewController,防止重复弹窗。
- 主线程操作:所有UI操作必须在主线程执行,Reachability回调默认在后台线程,必须切换线程。
- 内存管理:使用NotificationCenter时要在deinit中移除监听,避免内存泄漏。
- 无第三方依赖:全程仅使用系统框架,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Two Chains
相关产品推荐
相关产品推荐

