UINavigationController子类初始化注入额外依赖遇问题求解决方案
这个问题的核心是Swift初始化规则的约束:子类的指定初始化器必须调用父类的指定初始化器,而你当前的自定义初始化器违反了这条规则,导致系统尝试调用未实现的init(nibName:bundle:)而崩溃。
问题分析
你写的init(rootVC:authUser:)是一个指定初始化器(没有convenience关键字),但它调用了父类UINavigationController的便利初始化器init(rootViewController:)。根据Swift规则,指定初始化器只能调用父类的指定初始化器,不能调用便利初始化器。这就导致Swift要求你的子类必须实现父类的所有指定初始化器——也就是init(nibName:bundle:)——而你没有实现,所以初始化链触发这个方法时就抛出了致命错误。
同时,你的authUser是依赖注入的必要属性,不能用可选类型或默认值,所以必须确保所有初始化路径都能正确注入这个依赖。
正确解决方案
我们需要调整初始化逻辑,让自定义初始化器遵循Swift的初始化规则,同时保证authUser被正确注入:
class CustomNavController: UINavigationController { // 不可变的依赖属性,必须在初始化阶段赋值 let authUser: AppUser // 自定义指定初始化器:先初始化自身属性,再调用父类指定初始化器 init(rootVC: UIViewController, authUser: AppUser) { self.authUser = authUser // 调用父类的指定初始化器(UINavigationController的指定初始化器是init(nibName:bundle:)) super.init(nibName: nil, bundle: nil) // 手动设置根视图控制器,和init(rootViewController:)效果一致 self.viewControllers = [rootVC] } // 实现父类的指定初始化器,但禁止通过这个路径初始化(因为无法注入authUser) override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { fatalError("请使用 init(rootVC:authUser:) 初始化该控制器") } // 禁止Storyboard初始化 required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
为什么这样可行?
- 遵循初始化规则:自定义指定初始化器先初始化
authUser,再调用父类的指定初始化器init(nibName:bundle:),完全符合Swift的初始化链要求。 - 保证依赖注入:所有合法的初始化路径都必须传入
authUser,避免出现未初始化的依赖。 - 替代父类便利初始化器:手动设置
viewControllers = [rootVC],和调用init(rootViewController:)的效果完全一致,但绕过了父类的便利初始化器,避免了规则冲突。
额外说明
如果你需要兼容某些场景必须支持init(nibName:bundle:),可以修改该方法要求传入authUser,但这需要将其改为自定义指定初始化器(而非重写父类方法):
class CustomNavController: UINavigationController { let authUser: AppUser init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authUser: AppUser) { self.authUser = authUser super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } convenience init(rootVC: UIViewController, authUser: AppUser) { self.init(nibName: nil, bundle: nil, authUser: authUser) self.viewControllers = [rootVC] } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
这种方式将init(nibName:bundle:authUser:)作为指定初始化器,自定义的init(rootVC:authUser:)作为便利初始化器,同样能满足依赖注入的需求,但灵活性稍低——因为调用方必须知道nibName和bundle参数(即使传nil)。
内容的提问来源于stack exchange,提问作者Cognitio

