Swift中如何子类化UIViewController并正确初始化必填属性?
正确实现必须显式初始化属性的UIViewController子类
你遇到的问题核心是没理清Swift中UIViewController的初始化链,以及如何正确限制视图控制器的初始化方式。这里给你一个符合Swift惯例且安全的实现方案:
class MyController: UIViewController { // 用let确保属性一旦初始化就不可修改,完全符合你"必须显式初始化且不能为nil/有默认值"的要求 let prop: Int // 唯一允许的初始化器:必须传入prop init(prop: Int) { // 先初始化自己的存储属性,这是Swift的要求:调用super之前必须完成所有自有存储属性的初始化 self.prop = prop // 直接调用父类的指定初始化器,传入nil表示不依赖nib/Storyboard super.init(nibName: nil, bundle: nil) } // 禁止通过Storyboard/nib反序列化初始化 @available(*, unavailable, message: "请使用 init(prop:) 初始化该控制器") required init?(coder: NSCoder) { fatalError("此控制器不支持通过序列化初始化") } // 禁止直接调用nib初始化器,彻底堵死其他初始化路径 @available(*, unavailable, message: "请使用 init(prop:) 初始化该控制器") override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { fatalError("此控制器不支持通过nib初始化") } }
为什么之前的写法有问题?
- 调用
super.init()的坑:UIViewController的无参init()是一个便利初始化器,它内部会调用指定初始化器init(nibName:bundle:)。但Swift要求在调用super的任何初始化器之前,必须先完成自身所有存储属性的初始化——不过你之前的写法虽然先设了prop,但super.init()会触发父类的nib相关逻辑,这就是导致崩溃的原因。 - 重写
init(nibName:bundle:)的问题:你添加的这个重写是父类的指定初始化器,Swift要求它必须初始化所有自有存储属性,但你没给prop赋值,所以编译器报错。更关键的是,这个初始化器是你不希望被外部调用的,所以根本不应该去实现它,而是直接禁用。
这个方案的优势
- 编译层面的安全:通过
@available(*, unavailable)标记不需要的初始化器,编译器会直接阻止外部调用这些方法,从根源上避免有人误通过nib/Storyboard创建实例,彻底解决你担心的"得到默认值"问题。 - 符合Swift初始化规则:自定义的指定初始化器先完成自身属性初始化,再调用父类的指定初始化器,完全遵循Swift的初始化安全检查。
- 语义明确:用
let定义prop,清晰传达"这个属性是不可变的,且必须在初始化时指定"的意图。
关于你之前的临时方案
那种重复赋值的写法不仅不优雅,而且无法阻止外部调用禁用的初始化器——只要有人调用init(nibName:bundle:),还是会得到一个prop为默认值的实例,存在潜在风险。而上面的方案从编译阶段就杜绝了这种可能,是更健壮的做法。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

