为何相似Swift代码初始化顺序不同,致Outlet表现有差异?
为什么TableViewCell的willSet能正常访问Outlet,而ViewController的却不行?
先看两段代码的差异:
1. 运行正常的TableViewCell代码
class TableViewCell: UITableViewCell { @IBOutlet weak var fullName: UILabel! @IBOutlet weak var age: UILabel! var viewModel: TableViewCellViewModelType? { willSet(viewModel) { fullName.text = viewModel?.fullName age.text = viewModel?.age } } }
2. 报错(label为nil)的DetailViewController代码
class DetailViewController: UIViewController { @IBOutlet weak var label: UILabel! var viewModel: DetailViewModelType? { willSet(viewModel) { guard let viewModel = viewModel else { return } self.label.text = viewModel.description } } }
核心原因:两者的视图加载时机完全不同
对于TableViewCell:
当你在tableView(_:cellForRowAt:)方法中获取(或复用)cell时,系统已经完成了cell的视图加载和Outlet绑定。也就是说,在你给cell设置viewModel之前,fullName和age这些IBOutlet已经被正确连接到xib/storyboard上的控件了,所以willSet块里访问它们不会是nil。
对于ViewController:
在源VC的prepareForSegue方法执行时,目标DetailViewController虽然已经被实例化,但它的view还没有被加载!iOS中ViewController的视图遵循“懒加载”原则:只有当view被第一次访问时(比如即将显示到屏幕上),才会加载xib/storyboard,完成Outlet的绑定。
所以在prepareForSegue里设置viewModel时,willSet触发的瞬间,label还是nil,自然会报错。而把设置label的逻辑放到viewWillAppear里时,view已经完成加载,Outlet也已经连接好了,所以就能正常工作。
额外优化建议
如果想在ViewController里通过viewModel的setter来更新UI,建议改成使用didSet,并判断view是否已加载;或者直接在视图生命周期方法里主动读取viewModel属性更新UI,比如:
var viewModel: DetailViewModelType? { didSet { // 仅当视图已加载时更新UI if isViewLoaded { label.text = viewModel?.description } } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) label.text = viewModel?.description }
内容的提问来源于stack exchange,提问作者wm.p1us
相关产品推荐
相关产品推荐

