介绍 您好!我叫 Kiryl Famin,是一名 iOS 开发人员。 今天,我想彻底研究一下 Swift 中的初始化器这一简单主题。尽管它看起来很简单,但有时对这一主题缺乏 了解会导致令人沮丧的错误,人们希望快速修复这些错误,而不必深入研究细节。 全面 在本文中,我们将介绍与初始化程序相关的所有内容,包括: 如何在定义自定义结构时保留结构的成员初始化程序 为什么并不总是需要在类中编写初始化程序 为什么在指定的初始化程序中并不总是需要调用 super.init 为什么在调用 之前必须填充子类的所有字段 super.init 如何在子类中以最少的覆盖访问所有父类初始化器 何时需要一个 初始化程序 required 为什么 总是在没有参数的情况下被调用,但是 和 被覆盖 UIView.init() init(frame:) init(coder:) ...还有更多。让我们一步一步来。 目录 基础知识 结构 成员初始化器 可选 与 var let 保留成员初始化器 课程 指定初始化器 便利初始化器 保留超类的便捷初始化程序 尽量减少覆盖次数 编译器帮助 初始化器:泛型、协议、 、 required Self() final 无参数的 UIView() 荣誉提名 可失败初始化 枚举 概括 相关链接 基础知识 苹果的指南 (顺便说一句,其中对初始化器的描述出奇的详细)指出: 《Swift 编程语言(6)》 是准备类、结构或枚举的实例以供使用的过程。此过程涉及为该实例上的每个存储属性设置初始值,并执行新实例可供使用之前所需的任何其他设置或初始化。 初始化 您可以通过定义初始化程序来实现此初始化过程,这些 程序类似于可以调用来创建特定类型的新实例的特殊方法。与 Objective-C 初始化程序不同,Swift 初始化程序不返回值。它们的主要作用是确保在首次使用之前正确初始化类型的新实例。 初始化 好吧,我想我不需要在这里添加任何内容。 结构 我们先来讨论一下结构初始化器。由于没有继承,所以这很简单,但你仍然必须了解一些规则。 成员初始化器 我们来写一个简单的结构: struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool) 请注意,我们无需显式声明初始化器即可初始化结构。发生这种情况是因为结构接收由编译器生成的 。这 。 成员初始化器 仅适用于结构 通过选择 ,你可以看到它的样子: Refactor → Generate memberwise initializer init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked } 从签名中很容易看出,如果不为所有参数提供值,将导致编译错误: let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call 但是,如果您想减少所需参数的数量,您可以定义自定义初始化程序: init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅ 请注意,如果 未填充,这将导致编译错误,因为 。 isBlocked 所有结构属性都必须在初始化程序中填充 与 可选, var let 唯一不需要 填充字段的情况是当字段是 ( ) ( )时。在这种情况下,该字段将默认为 : 明确 可选 ? 变量 var nil struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅ 但是,如果我们在这种情况下尝试使用成员初始化器,我们将收到编译错误: let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call 保留成员初始化器 发生这种情况的原因是声明自定义初始化器会删除成员初始化器。仍然可以显式定义它,但它不会自动可用。 但是,有一个保留成员初始化器的小技巧:在 中声明自定义初始化器。 extension struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false 结构摘要 所有字段都必须在初始化程序中填充 可选 var 字段默认为 nil 结构接收一个免费的成员初始化器 如果声明了自定义初始化器,则成员初始化器会消失 要保留结构的成员初始化器,请在 extension 中定义自定义初始化器 课程 指定初始化器 类的主要初始化器是 。它有两个用途: 指定初始化器 确保所有字段都已填充 如果该类被继承,则调用超类初始化程序 class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } } 在调用 所有字段。这是因为超类初始化程序可能会调用子类重写的方法,而这些方法可能会访问未填充的子类属性。 super.init 之前必须填充 class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } } 因此,如果我们没有设置 ,我们就会遇到运行时错误,因为 初始化程序会从 类调用重写的 方法。此方法尝试访问 属性,但该属性尚未填充。 self.breed = breed Animal Dog getInfo() breed 与结构不同,类不会接收隐式成员初始化器。如果存在未初始化的属性,则会发生编译错误: class Animal { // ❌ Class 'Animal' has no initializers var age: Int } class Animal { // ✅ var age: Int = 0 } class Animal { // ✅ var age: Int? } class Animal { } // ✅ 便利初始化器 类也可以有一个 。与指定初始化器不同,它们不是从头开始创建对象,而是通过重用其他初始化器的逻辑来简化初始化过程。 便捷初始化器 class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } } 便利初始化器可以调用指定初始化器或其他便利初始化器。最终,指定初始化器将始终被调用。 便利初始化器总是水平放置(self.init),而指定初始化器则垂直放置(super.init)。 保留超类的便利初始化程序 一旦子类声明了新属性,它就无法访问所有超类的便利初始化程序。 class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call 可以通过重写 来解决此问题。 所有超类的指定初始化程序 class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ 很容易看出,以这种方式,要在下一个子类中使用便捷初始化程序,您需要重写 初始化程序。 两个 class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 尽量减少覆盖次数 但是,可以通过使用 来避免这种情况。 便捷覆盖初始化程序 class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 现在每个子类中只有 2 个明确指定的初始化器。 指定的 init 而不是 。 注意便捷重写初始化程序如何调用 self super.init 我强烈推荐这本书,作者是 Tjeerd in 't Veen,他在 的第 5 章中详细解释了这个技巧。 《Swift in Depth》 中级总结 确保所有属性都已填充并调用 。 指定的初始化程序 super.init() 通过调用指定初始化程序来简化初始化。 便捷初始化程序 如果子类声明了新属性, 将无法使用。 则便捷初始化程序 要恢复超类的 ,必须重写其所有指定的初始化器。 便捷初始化器 为了最大限度地减少覆盖的次数,可以使用 。 便捷覆盖初始化程序 编译器帮助 我们已经讨论过,如果子类不引入新参数,它会自动继承所有超类的初始化器。 class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅ 但是,还有一点很重要:如果超类只有一个指定的初始化器,并且它是无参数的(没有参数的 ),那么子类中显式声明的初始化器 调用 。在这种情况下,Swift 编译器 对可用的无参数 调用。 init() 不需要 super.init() 会自动插入 super.init() class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } 代码编译通过,因为 被隐式调用。这对于以下一些示例至关重要。 super.init() 必需的 在所有子类必须具有与基类相同的初始化程序的情况下,都使用 初始化程序。它还必须调用 。以下是 初始化程序的示例。 required super.init() required 泛型 只有将泛型类型声明为必需的 才能调用其 。 init required init class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer } 这段代码无法编译,因为 对 的子类一无所知。虽然在这个特殊情况下, 确实有一个没有参数的 ,但想象一下如果它引入了一个新字段: Factory Base Subclass init() class Subclass: Base { let value: Int init(value: Int) { self.value = value } } 这里,它不再具有空的 ,因此必须按 进行声明。 init required class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance() 请注意,即使我们没有在 中明确声明 ,编译器也会为我们生成它。这在 中进行了讨论。 被自动继承并称为 。 Subclass required init Compiler Assistance required init super.init() class Subclass: Base { required init() { super.init() } } 协议 协议中声明的所有初始化程序都必须是 : required protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' } 再次强调,这是必要的,因为编译器可以确保子类实现协议初始化器。我们已经知道,这并不总是会发生 — 如果 不是 ,子类就没有义务覆盖它,并且可以定义自己的初始化器。 init required class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init() 当然,下面的代码将无法编译,因为 。 required Base.init() class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } } 自己() 在静态方法中调用 初始化程序时也会发生类似的情况。 Self() class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer } 一如既往,问题在于继承: class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌ class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } } 摆脱 : required final 由于 的目的是强制在子类中实现初始化程序,因此,自然地,使用 关键字禁止继承就不需要将初始化程序标记为 。 required final required protocol Initable { init() } final class InitableObject: Initable { } // ✅ protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ } 中级摘要 如果子类没有引入新的参数,它会自动从其超类继承所有初始化器。 如果超类只有没有参数的 ,则会在子类初始化程序中自动调用它。 init() 需要一个 初始化程序来保证其存在于子类中以供泛型、协议和 使用。 required Self() 用户界面视图() 简要提到了没有参数的 初始化程序,它在文档中找不到,但却在各处神秘地使用。 UIView() 原因是 继承自 ,而 NSObject 有一个无参数的 。 ,这个初始化器没有在 接口中明确声明,但它仍然可用: UIView NSObject init() 因此 UIView @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init() 然而,在底层,此初始化程序在代码中初始化时会调用 在通过 Interface Builder 初始化时会 。发生这种情况是因为 提供了自己的 实现,这一点可以通过 返回 和 的不同地址这一事实得到证实。 init(frame:) init(coder:) UIView NSObject.init() method_getImplementation NSObject.init() UIView.init() 荣誉提名 可失败初始化 可失败的初始化只是返回一个可选的 final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } } 枚举 具有原始值的枚举可获得免费的 init?(rawValue:) enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north") 您还可以为枚举创建自定义初始化。所有枚举初始化都必须赋值 。 self enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } } 最终总结 我们已经介绍了 Swift 中初始化程序的所有基本方面: 在初始化程序中,所有字段都必须填充。 可选的 属性默认为 。 var nil 结构接收一个免费的 。 成员初始化器 当定义自定义初始化程序时, 。 成员初始化程序就会消失 确保所有字段都已填充并调用 。 指定的初始化程序 super.init() 通过调用指定初始化程序来简化初始化。 便捷初始化程序 便利初始化器总是 ( ),而指定初始化器则 ( )。 水平放置 self.init 垂直放置 super.init 如果子类声明了新属性, 将无法使用。 则便捷初始化程序 要恢复超类的 ,必须重写其所有指定的初始化器。 便捷初始化器 为了最大限度地减少覆盖的次数,可以使用 。 便捷覆盖初始化程序 如果子类没有引入新的参数,它会自动从其超类继承所有初始化器。 如果超类只有没有参数的 ,则会在子类初始化程序中自动调用它。 init() 确保其存在于子类中以供泛型、协议和 使用。 必需的初始化程序 Self() 调用 或 。 UIView.init() UIView.init(frame:) UIView.init(coder:) 返回一个可选项。 可失败的初始化程序 具有原始值的枚举可获得免费的 。 init?(rawValue:) 所有枚举初始值设定项都必须赋值 。 self 希望本文对您有所帮助。如果您还有任何不清楚的地方或发现不准确之处,请随时通过 Telegram 联系我获取免费解释: 。 @kfamyn 相关链接 Swift 编程语言(6)/ 初始化 Tjeerd in 't Veen 的 Swift in Depth 电报-@kfamyn