在 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。
- (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;
}
}
}
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
}
as? 可選綁定,避免強轉崩潰。children 替代 childViewControllers:Swift 3+ 推薦用法。直接在 leftViewDidHide 中硬編碼導航堆疊索引屬於「強耦合」,後期調整導航結構極易崩潰。建議採用以下模式之一:
// 發送端(側邊欄關閉時)
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
}
// 側邊欄控制器定義
protocol SideMenuDelegate: AnyObject {
func sideMenuDidHide()
}
// 容器 VC 持有 delegate,關閉時調用
weak var delegate: SideMenuDelegate?
func closeMenu() {
delegate?.sideMenuDidHide()
}
// SGServerListViewController 遵守協議
extension SGServerListViewController: SideMenuDelegate {
func sideMenuDidHide() {
serverTableView.isUserInteractionEnabled = true
}
}
使用單例或 ViewModel 持有 isSideMenuOpen 狀態,VC 通過 KVO 或 Combine 訂閱變化。
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 索引檢測 (count > 1) | 改動最小、快速修復 | 仍強耦合導航結構 | 緊急修復、舊專案維護 |
| NotificationCenter | 完全解耦、一對多 | 隱式依賴、難追踪 | 跨模組、多監聽者 |
| Delegate/Closure | 顯式契約、編譯期檢查 | 一對一、需持有引用 | 明確父子/協作關係 |
| 共享狀態 | 響應式、狀態單一來源 | 引入框架依賴 | 現代 SwiftUI/Combine 架構 |
「Index out of bounds」是典型的防禦性編程缺失。**立即修復**請加 count 判斷;**長期治理**請採用 Notification/Delegate/狀態管理解除對導航堆疊結構的硬依賴。Swift 的 guard let 鏈式解包能以最優雅的方式實現安全訪問。