首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

iOS 導航控制器堆疊中安全檢測視圖控制器是否存在

     編輯:程式設計綜合問答

問題背景

在 iOS 開發中,常需要從 AppDelegate 或側邊欄選單中訪問導航堆疊(UINavigationController.viewControllers)中的特定視圖控制器(VC)並操作其屬性。原始代碼直接通過硬編碼索引 objectAtIndex:1 訪問,導致當堆疊層數不足時發生 「Index out of bounds」崩潰。

錯誤代碼分析

- (void)leftViewDidHide {
    // 危險:直接假設索引 1 存在
    if ([((AppDelegate *)[UIApplication sharedApplication].delegate).frontViewController.navigationController.viewControllers objectAtIndex:1]) {
        SGServerListViewController *sample = [[[((AppDelegate *)[UIApplication sharedApplication].delegate).frontViewController.navigationController.viewControllers objectAtIndex:1] childViewControllers] objectAtIndex:0];
        [sample.serverTableView setUserInteractionEnabled:YES];
    }
}

崩潰原因:viewControllers 陣列長度可能小於 2(即索引 1 不存在),直接取值拋出 NSRangeException。

正確檢測模式:先判斷 count 再取值

- (void)leftViewDidHide {
    UINavigationController *nav = ((AppDelegate *)[UIApplication sharedApplication].delegate).frontViewController.navigationController;
    NSArray *vcs = nav.viewControllers;

    // 1. 先檢查陣列長度
    if (vcs.count > 1) {
        UIViewController *targetVC = vcs[1]; // 安全訪問索引 1
        
        // 2. 再檢查 childViewControllers
        if (targetVC.childViewControllers.count > 0) {
            SGServerListViewController *sample = (SGServerListViewController *)targetVC.childViewControllers[0];
            sample.serverTableView.userInteractionEnabled = YES;
        }
    }
}

Swift 現代寫法(推薦)

func leftViewDidHide() {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
          let nav = appDelegate.frontViewController?.navigationController,
          nav.viewControllers.count > 1,
          let targetVC = nav.viewControllers[1] as? YourContainerVC,
          let sample = targetVC.children.first as? SGServerListViewController
    else { return }

    sample.serverTableView.isUserInteractionEnabled = true
}
  • Guard 語句鏈式解包:任何一環失敗直接 return,代碼扁平化、無巢狀 if。
  • 類型轉換安全:使用 as? 可選綁定,避免強轉崩潰。
  • children 替代 childViewControllers:Swift 3+ 推薦用法。

更健壯的架構建議:解耦合

直接在 leftViewDidHide 中硬編碼導航堆疊索引屬於「強耦合」,後期調整導航結構極易崩潰。建議採用以下模式之一:

方案 A:NotificationCenter 廣播通知

// 發送端(側邊欄關閉時)
NotificationCenter.default.post(name: .sideMenuDidHide, object: nil)

// 接收端(SGServerListViewController)
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(enableTableInteraction), name: .sideMenuDidHide, object: nil)
}

@objc private func enableTableInteraction() {
    serverTableView.isUserInteractionEnabled = true
}

方案 B:Delegate / Closure 回調

// 側邊欄控制器定義
protocol SideMenuDelegate: AnyObject {
    func sideMenuDidHide()
}

// 容器 VC 持有 delegate,關閉時調用
weak var delegate: SideMenuDelegate?
func closeMenu() {
    delegate?.sideMenuDidHide()
}

// SGServerListViewController 遵守協議
extension SGServerListViewController: SideMenuDelegate {
    func sideMenuDidHide() {
        serverTableView.isUserInteractionEnabled = true
    }
}

方案 C:共享狀態

使用單例或 ViewModel 持有 isSideMenuOpen 狀態,VC 通過 KVO 或 Combine 訂閱變化。

總結對比

方案優點缺點適用場景
索引檢測 (count > 1)改動最小、快速修復仍強耦合導航結構緊急修復、舊專案維護
NotificationCenter完全解耦、一對多隱式依賴、難追踪跨模組、多監聽者
Delegate/Closure顯式契約、編譯期檢查一對一、需持有引用明確父子/協作關係
共享狀態響應式、狀態單一來源引入框架依賴現代 SwiftUI/Combine 架構

結語

「Index out of bounds」是典型的防禦性編程缺失。**立即修復**請加 count 判斷;**長期治理**請採用 Notification/Delegate/狀態管理解除對導航堆疊結構的硬依賴。Swift 的 guard let 鏈式解包能以最優雅的方式實現安全訪問。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved