Swift View和Controller共享变量初始化麻烦

Swift View and Controller shared variable initialization troubles

我正在将一些代码从 Objc 移植到 Swift。并与 Swift 的初始化生命周期作斗争。从简化的角度来看,我在这里尝试让 4 位玩家发挥作用:

  1. 一个名为 Program 的对象。这是我目前主要的顶级模型对象。剩下的3个玩家都想获得他的实例
  2. 一个ProgramEditController,画在我的主Main.storyboard。他负责实例化一个初始程序,这不能作为 属性 初始化器直接完成。
  3. 一个顶级自定义 UIView 子类,称为 ProgramTimelineView。 Painted in Main.storyboard,管理各种专门的子视图。链接到我的 ViewController 的 属性。也有它的子视图的属性。它想要访问程序,所以它可以进行布局并将其传递给子视图。
  4. ProgramTimelineView 的一个特定子视图称为 ProgramGridView。这些不是在 XCode canvas 工具中绘制的,而是通过包含 ProgramTimelineView 直接实例化的。它想要访问该程序。用它来做他的习惯 drawRect.

这是我的控制器的相关代码:

class ProgramEditController: UIViewController { 
    // MARK: - Variables
    @IBOutlet var timelineView:ProgramTimelinesView!

    var site = Site()

    var program:Program!

    // MARK: - Initialize

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        // set up the new program
        self.program = self.site.newProgram() 
        // get it into our top view before it starts drawing
        self.timelineView.program = self.program 
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder) // Why does Swift make me have a redundant thing here?
    }

}

对于 ProgramTimelinesView:

class ProgramTimelinesView: UIView {
    // MARK: - Variables
    var gridView = ProgramGridView()

    var program:Program! {
        didSet {
            self.gridView.program = self.program
        }
    }

    // MARK: - Initialization

    func addGridView() {
        self.gridView.alpha = 0.0
        self.gridView.opaque = false
        self.addSubview(self.gridView)
    }

    func commonInit() {
        self.addGridView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

}

最后 ProgramGridView:

class ProgramGridView: TimeAxisView {
    // MARK: - Variables
    var program:Program!

    override func drawRect(rect: CGRect) {
        // access self.program in here
    }
}

我认为会发生什么:

  1. ProgramEditController.init(nibName...) 会先触发。
  2. super 调用会导致我的 ProgramTimelineView.init(coder) 触发。
  3. ProgramTimelineView 实例将首先调用 gridView 初始化程序,将其设置为新的 ProgramGridView 视图
  4. ProgramTimelineView.init(coder) 的其余部分将 运行,这会将 gridView 添加到视图树中。
  5. Control 会 return 到 ProgramEditController.init(nibName) 初始值设定项。将填充控制器的 program 属性。
  6. 绑定 timelineView 将设置 program 属性。
  7. ProgramTimelineView 将依次设置 ​​gridView.
  8. program 属性

不过,在第 4 步和第 5 步之间似乎发生了 drawRect()。这会导致段错误,因为尚未设置 gridView 的程序!但为什么它在此时发出 drawRect()?我认为在所有初始化器都被触发之前不会发生这种情况。但很明显,一些副作用正在发生。避免这种情况的正确 pattern/idiom 是什么?我真的不想把所有的 program! 都变成 program? 然后把 let/guards 放在每个地方。

事实证明我原来的假设是错误的(通常是这样)。

UIViewController.init(nib...) 在从界面生成器资产创建时不会被调用。但是在接口构建器中链接的局部变量(隐式包装)在 init(coder) 点也不是 set/realized 。正确的方法需要两个调整:

  1. program var 的设置移动到 init(coder) 初始值设定项:

例如

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.program = self.site.newProgram()
}
  1. 将其转发到 viewDidLoad() 覆盖中的视图。

例如

override func viewDidLoad() {
    super.viewDidLoad()
    self.timelineView.program = self.program
}