swift - 在 Swift 中初始化 UIViews 的惯用模式

标签 swift uiview initialization

Objective-C 中,我发展了同时拥有 awakeFromNibinitWithFrame: 方法的模式,后者调用了它们的 super 的,然后调用一个 _commonInit,我把我自己的所有代码都放在那里。例如

- (void)_commonInit {
    // Initialize stuff here
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self _commonInit];
    }
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    [self _commonInit];
}

所以我试图在我移植到 Swift 的 UIView 子类中重用这个模式:

func _commonInit() {
    // initialize code here
}

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

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

override func awakeFromNib() {
    super.awakeFromNib()
    self._commonInit()
}

这是正确的做法吗?我很好奇为什么 init(coder...)必需的。特别是当我所做的只是调用 super 版本时。我似乎记得我在 Objc 版本中使用 awakeFromNib 的原因是因为从 nib 恢复应用的任何更改直到晚于 initFromCoder:.

最佳答案

拥有一个从你的初始化器调用的 commonInit 方法对于你有多个指定(或要求)初始化器的情况来说是一个完美的模式。不过,这不是唯一的模式。

依次解决你的每一个疑惑(并补充一些相关的要点)...

  • init(coder:) 是必需的,因为您正在子类化声明符合 NSCoding 协议(protocol)的类。该协议(protocol)要求所有子类的所有实例都能够从存档中初始化。

    不过,您实际上不必在 init(coder:) 中执行任何操作,除非您将状态保存在 encodeWithCoder(_:) 中。但是因为子类可能具有未继承的可编码状态,所以初始化安全要求子类负责此初始化程序(即使它所做的只是调用 super)。

  • 您在从 nib/ Storyboard加载的任何自定义类中使用 awakeFromNib() 来处理仅在连接对象的导出和操作后才需要发生的初始化。

    当 Cocoa (Touch) 加载一个 nib 时,它首先初始化每个对象(使用 init(coder:) ),然后在所有对象“事件”之后,它连接所有 IBOutlet 变量和控件发送的所有 IBAction 的目标.完成所有这些后,它调用 awakeFromNib()

  • 在 Swift 中使用 commonInit 模式有一个警告——甚至是一个悖论:您必须在调用 super.init() 之前初始化属性(又名实例变量),并且您不能访问 self(包括调用 self 上的方法) ) 直到调用 super.init() 之后。因此,您不能使用 commonInit 方法来设置属性的初始值。

    可以具有类型为隐式展开的可选类型的属性。这些会自动初始化为 nil ,然后您可以在 commonInit 方法中设置一个“真实的”初始值:

    var name: String!
    init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }
    init(coder: NSCoder) {
        super.init(coder: coder)
        self.commonInit()
    }
    private func commonInit() {
        name = // something...
    }
    

解决此问题的另一种模式是为每个属性提供初始值设定项(甚至惰性初始值设定项)。如果这样做,则不需要 commonInit 方法,因为将从实际使用的 init 中隐式调用属性初始值设定项(或者在惰性初始值设定项的情况下,当首次访问属性时)。

class MyView: UIView {
    let fileManager = NSFileManager.defaultManager()
    let appDelegate = UIApplication.sharedApplication().delegate as! MyAppDelegate
    lazy var name: String = { /* compute here */ }()
    init(frame: CGRect) { super.init(frame: frame) }
    init(coder: NSCoder) { super.init(coder: coder) }
    // ...
}

最后,如果你确实提供了一个 commonInit(或类似的)方法,你不需要用初始下划线或任何东西来破坏名称——Swift 有内置的访问控制,所以任何您不想向类外的调用者公开的方法可以简单地标记为 private

关于swift - 在 Swift 中初始化 UIViews 的惯用模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29956195/

相关文章:

ios - 以编程方式创建带有按钮目标的自定义 View

ios - 将点击操作添加到 NavigationController 中的自定义 View

swift - 以 AVCaptureVideoPreviewLayer 作为子层旋转 UIView

c++ - 在C++中初始化结构的成员变量

构造函数/函数重载签名查找时间复杂度?

SwiftUI: ListView 与 ForEach View

ios - 当值为空字符串时,TextField 不显示底线 Material Swift

java - 转换JSP java代码

ios - tableviewcell 的附件 View 中的 UISwitch,通过选择器传递参数以使选择器函数看到 indexpath.row?

ios - Xcode 错误地设置为使用旧版构建系统