如果我知道init?(coder:)
并且不会调用其他storyboard / nib特定的初始值设定项,我是否可以避免在UI子类中实现或调用它们的要求?
许多UIKit课程包括UIViewController
,UIView
和UIControl
(UIButton
,UITextField
等的子类)采用NSCoding
协议。 NSCoding
init?(coder:)
方法用于从故事板或笔尖实例化这些类。
NSCoding
协议:
public protocol NSCoding {
func encode(with aCoder: NSCoder)
init?(coder aDecoder: NSCoder)
}
采用带初始化程序的协议的类必须将该初始化程序实现为required
。必需的初始值设定项必须由所有子类实现。
我经常构建没有故事板或笔尖的iOS应用程序。我在代码中完全实现了UIViewController,UIView和UIControl子类。但是,如果我想提供自己的init?(coder:)
方法(我经常这样做),我必须在子类中实现init
来安抚编译器。以下示例说明了这一点。
以下内容无法编译
class CustomView: UIView {
init() {
super.init(frame: CGRect.zero)
}
}
// Error:'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'
以下编译,因为我提供了init?(coder:)
的实现。对于仅代码的UI子类,我通常通过抛出一个致命错误来声明我不希望它被调用来实现'init(coder :)'。
class CustomView: UIView {
init() {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
由于上述原因,CustomView的子类还需要实现'init(coder:)'。
class SubClassOfCustomView: CustomView {
let someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
答案 0 :(得分:2)
*以下代码是在Swift 3中编写和测试的
此解决方案的关键是创建自定义UI子类继承的基类。在下面的示例中,这些子类名为BaseViewController
,BaseView
和BaseButton
。这些子类包含一个初始化程序,它默认超类的指定初始化程序的参数,这些参数对于它们的子类是隐藏的。
init(coder:)
必须在所有子类中实现,因为它是UI超类的必需初始化程序。您可以通过将属性@available(*, unavailable)
置于该初始化程序的实现之上来解决此问题。
The available
attribute用于“表示声明相对于某些平台和操作系统版本的生命周期”。将此属性与以下参数一起使用:@available(*, unavailable)
使得在所有平台的所有版本上都不可用的代码块。
class BaseViewController: UIViewController {
// This initializer hides init(nibName:bundle:) from subclasses
init() {
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
class CustomViewController: BaseViewController {
let someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}
let customViewController = CustomViewController(someProperty: 1)
class BaseView: UIView {
// This initializer hides init(frame:) from subclasses
init() {
super.init(frame: CGRect.zero)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
class CustomView: BaseView {
let someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}
let customView = CustomView(someProperty: 1)
此UIButton示例说明了如何子类化UIControl子类。
internal class BaseButton: UIButton {
// This initializer hides init(frame:) from subclasses
init() {
super.init(frame: CGRect.zero)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
class CustomButton: BaseButton {
let someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}
let customButton = CustomButton(someProperty: 1)
我不建议定期使用@available(*, unavailable)
来避免实现所需的初始化程序。它有利于减少在这种情况下不会被调用的冗余代码(因为您不打算使用storyboard / nib)。 @available(*, unavailable)
的外观通过在基类中使用它(并且具有从基类继承的自定义子类)而不是在每个自定义子类中来减少。
我知道这在Swift 2和3中有效。未来版本的Swift可能不允许这样做。但是,我希望Swift团队提出一种更好的方法来避免自定义UI子类中的冗余代码。
出于好奇,我尝试从故事板中启动BaseViewController子类。我预计应用程序会因找不到选择器错误而崩溃,但它会调用init?(coder)
方法,即使它已从所有平台隐藏。这可能是因为available
属性没有隐藏Objective C中的init?(coder)
初始值设定项,而且这是故事板实例化代码运行的地方。
UIKit经常使用类和继承,而Swift社区鼓励结构和面向协议的编程。我在基本UI类声明之上包含以下标题,以阻止基本UI类成为全局设置和功能的转储基础。
/**
* This base UIViewController subclass removes the requirement to override init(coder:) and hides init(nibName:bundle:) from subclasses.
* It is not intended to create global functionality inherited by all UIViewControllers.
* Alternatively, functionality can be added to UIViewController's via composition and/or protocol oriented programming.
*/
/**
* This base UIView subclass removes the requirement to override init(coder:) and hides init(frame:) from subclasses.
* It is not intended to create global functionality inherited by all UIViews.
* Alternatively, functionality can be added to UIView's via composition and/or protocol oriented programming.
*/
参考:我发现Swift语言指南的Initialization部分有助于理解初始化器的规则。