本篇文章中主要涉及Class类的继承部分,会从required构造函数,到super.init()初始化父类部分谈起。全面梳理Class类的继承流程,通过代码样例等方式,学习并理解required关键词、super关键词等、父类和子类的构造函数、初始化等内容部分。
required简介
required 是 Swift 中用于构造函数(初始化方法)的关键字,表示这个构造函数必须在所有子类中实现。也就是说,如果父类的构造函数标记为 required,任何继承该类的子类都必须实现这个构造函数,甚至如果子类没有显式实现,它也会自动继承父类的 required 构造函数。
为什么要使用 required
使用 required 的主要原因是确保在继承链中,特定的初始化逻辑能够被所有子类继承和保持一致。通常在以下情况下会用到:
1、保证子类初始化的一致性:
如果你希望所有子类都具有某个特定的初始化方法,或者必须遵循相同的初始化规则,可以在父类中使用 required。
这样,无论子类是否显式实现了这个构造函数,它们都会自动继承。
2.、配合协议和 Codable 使用:
在 Codable 的场景中,类需要一个可以从 Decoder 初始化的构造函数 init(from:)。如果你希望这个类可以被子类化,并且所有子类都能够使用相同的方式进行解码,那么将 init(from:) 标记为 required 是很有用的。
这样做可以确保即使是子类,也能够通过相同的初始化方法进行解码。
class Animal {
var name: String
// required initializer
required init(name: String) {
self.name = name
}
}
class Dog: Animal {
// Automatically inherits the required initializer from the superclass
}
在上面的代码中,Animal 类有一个 required 构造函数。Dog 继承了 Animal,即使没有显式地定义 init(name:),也自动获得了这个构造函数。
举一反三
子类定义构造函数
根据上面的样例代码,如果我们想要Dog子类拥有自己的构造函数。
class Dog: Animal {
var type: String
init(type: String) {
self.type = type
}
}
这时Xcode会提示我们:required初始化程序init(name:)必须由Animal的子类提供。
'required' initializer 'init(name:)' must be provided by subclass of 'Animal'
Insert '
required init(name: String) {
fatalError("init(name:) has not been implemented")
}
'
这个错误的原因是Animal类中定义的init(name:)构造函数被标记为required。这意味着任何继承Animal的子类都必须实现这个required标记的构造函数。如果子类没有显式的提供required init(name:),编译器就会报错。
为什么给子类添加构造函数会报错呢?
这是因为我们在Dog子类中自定义了一个init(type:)构造函数。根据Swift的规则,如果子类定义了自己的构造函数,它就不会自动继承父类的构造函数,包括required构造函数。这就是为什么编译器提示需要显式的提供required init(name:)。
解决方案为:给Dog子类中添加父类的required构造函数。
class Dog: Animal {
var type: String
init(type: String) {
self.type = type
}
required init(name: String) {
self.name = name
}
}
子类初始化父类部分
下面是我们子类初始化父类部分,主要涉及如何通过super.init()调用父类的构造函数。
当我们运行上面的代码,给Dog子类自定义init(type:)构造函数之后,通过required重新实现父类的required构造函数之后,我们的Xcode仍然会报错:
'super.init' isn't called on all paths before returning from initializer
'self' used in property access 'name' before 'super.init' call
这是因为在Swift中,子类在初始化时必须确保父类的属性和状态被正确设置。Swift需要先初始化父类的部分,才能继续完成子类的初始化。这是对象初始化的一般规则,确保整个对象层级的所有部分都能够被正确配置。
父类Animal中,有一个属性name。这个属性是属于Animal的,当创建Dog实例时,Dog不仅有自己的属性(如type),还会继承Animal的属性(如name)。
因为只继承了属性,所以必须通过调用super.init(name:)来初始化它。
super.init(name: name) // 调用父类的初始化方法
如果子类的初始化方法没有调用父类的构造函数,父类的属性就无法被正确初始化,从而导致对象不完整并引发编译错误。
所以,Swift强制要求子类初始化时调用父类的构造函数,确保整个对象层级中每一部分的状态都被妥善处理。
因此,我们需要给Dog子类自定义的构造函数以及父类的required构造函数中,都添加super.init()。
class Dog: Animal {
var type: String
init(name:String, type: String) {
self.type = type
super.init(name: name)
}
required init(name: String) {
self.name = name
super.init(name: name)
}
}
在Dog子类自定义的构造函数中:如果Dog子类定义了一个自定义的构造函数,比如init(name:String, type: String),那么就必须调用super.init(name:name)才能确保Animal的属性name被正确初始化。
因为Animal类中的init(name:)是required,子类Dog必须提供一个实现。所以也需要在这个构造函数中调用super.init(name:name),否则父类的name属性将无法被初始化。
那么是否可以不使用super.init?
答案是不行的,每个子类的构造函数都必须调用super.init,否则编译器就会报错。只有以下几种情况,可以不显式调用super.init:
- 子类没有定义自己的属性:如果子类没有添加任何新属性,并且不需要额外的构造逻辑,那么 Swift 可以自动继承父类的构造函数,你不需要手动写 super.init。
- 子类使用默认的构造函数:如果你没有在子类中定义任何构造函数,并且父类有默认的构造函数,子类会自动继承它。此时你无需显式调用 super.init。
因此,子类的构造函数都必须调用super.init,以确保父类的属性得到正确初始化。
这是Swift确保类继承链中所有属性都能得到完整初始化的一种方式。
初始化顺序
那么,我们的代码现在无问题了么?还是有报错。现在的报错是:
'self' used in property access 'name' before 'super.init' call
Property 'self.type' not initialized at super.init call
这次报错的原因是Dog子类声明的required构造函数没有初始化type,以及在调用super.init之前,使用了self访问name属性。
self.type = "Type" // 在required构造函数中初始化type
required构造函数之所以需要初始化type,是因为Swift 要求所有属性在构造函数结束之前必须被初始化。子类的 required 构造函数也需要遵守这个规则。因此,即使是继承自父类的 required 构造函数,子类的属性仍然需要在这个函数中得到初始化。
这两个问题在于,我们如果想要Dog子类使用required构造函数,那么我们也应该给Dog子类的属性进行初始化,因此,在required构造函数中应该添加self.type。
required init(name: String) {
self.name = name
self.type = type
super.init(name: name)
}
我们在添加self.type之后,我们需要type拥有required构造函数的入参。
required init(name: String,type: String) {
self.name = name
self.type = type
super.init(name: name)
}
当我们修改required构造函数,添加type入参时,又会报新的报错:
Invalid redeclaration of 'init(name:type:)'
'required' initializer 'init(name:)' must be provided by subclass of 'Animal'
这个报错在于required初始化程序init(name:)必须由Dog子类提供,required关键字的构造函数必须严格匹配父类定义,所以我们无法任意修改参数列表,让Dog子类进行传参。
所以,Dog子类的type没有默认值,那么我们只能给Dog子类的type进行赋值的方法:
required init(name: String,type: String) {
self.name = name
self.type = "Type"
super.init(name: name)
}
赋值之后,就只剩下最后一个报错了:
'self' used in property access 'name' before 'super.init' call
这个报错是在访问或修改继承父类的self属性之前,应该先使用super.init进行父类的初始化。因为当前对象还没有完全初始化,
因此,我们只能先用super.init进行父类的初始化后,再使用self.name进行赋值。此外,因为我们required构造函数就是self.name = name,我们只需要调用父类的构造函数即可。
required init(name: String) {
self.type = "Type"
super.init(name: name)
}
最后,我们的代码就正常运行。
这里我们了解到required构造函数中,应先初始化子类的属性(type),然后尽早调用super.init(name:),确保子类和父类的所有部分都正确初始化。
此外,在super.init之前,不要访问父类self的属性。
完整代码:
class Animal {
var name: String
// required initializer
required init(name: String) {
self.name = name
}
}
class Dog: Animal {
var type: String
init(name:String, type: String) {
self.type = type
super.init(name: name)
}
required init(name: String) {
self.type = "Type"
super.init(name: name)
}
}