swift 学习- 16 -- 构造过程 02
// 类的继承 和 构造过程
// 类里面的所有的存储型属性 -- 包括所有继承自父类的属性 -- 都必须在构造过程中设置初始值
// Swift 为类类型提供了 两种构造器来确保实例中所有的存储属性都能获得初始值, 他们分别是 指定构造器 和 便利构造器
// 指定构造器 和 便利构造器
// 指定构造器是类中最主要的构造器, 一个指定构造器 将初始化类中提供的所有属性, 并根据父类链往上调用父类的构造器来实现父类的初始化
// 每一个类都必须拥有至少一个指定的构造器, 在某些情况下, 许多类通过继承父类中的指定构造器而满足了这个条件
// 便利构造器 是类中比较次要的, 辅助型的构造器, 你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值, 你也可以定义便利构造器来创建一个特特殊用途或特定输入值的实例
// 你应当只在必要的时候为类提供 便利构造器, 比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省开发时间并让类的构造过程更清晰明了
// 指定构造器 和 便利构造器的 语法
// 类的指定构造器的写法 与 值类型的简单构造器一样
// init(parameter){
// statements
// }
// 便利构造器也采用相同样式的写法, 单需要在 init 关键字之前放置 convenience 关键字, 并使用空格将他们俩分开
// 类的构造器代理规则
// 为了简化指定构造器和便利构造器之间的调用关系, Swift 采用一下三条规则来限制构造器之间的代理调用
// 1 : 指定构造器 必须调用 其直接父类的 指定构造器
// 2 : 便利构造器 必须调用 同类中定义的 其他构造器
// 3 : 便利构造器 必须最终 导致一个指定构造器 被调用
// 一个更方便记忆的方法是:
// 指定构造器 必须总是 向上代理
// 便利构造器 必须总是 横向代理
// 注意 : 这些规则不会影响类的实例如何创建, 任何上图中展示的构造器都可以用来创建完全初始化的实例
// 两段式构造过程
// Swift 中类的构造过程包含两个阶段, 第一个阶段, 每个存储属性被引入它们的类指定一个初始值, 当每个存储属性的初始值被确定后, 第二阶段开始, 它给每个类一次机会, 在新实例准备使用之前进一步定义它们的存储型属性
// 两段式构造过程的使用让构造过程更加安全, 同时在整个类层级结构中给予了每个类完全的灵活性, 两段式构造过程可以防止属性值在初始化之前被访问, 也可以防止属性被另外一个构造器意外地赋予不同的值.
// 注意 : Swift 的两段式构造过程跟 OC 中的构造过程类似, 最主要的区别在于阶段 1 , OC 给每一个属性赋值 0 或 空值. Swift 的构造流程更加灵活, 它允许你设置定制的初始值, 并自如应对某些属性不能以 0 或 nil 作为合法默认值的情况
// Swift 编译器将执行 4 种有效的安全检查, 以确保两段式构造过程能不出错的完成
// 安全检查 1:
// 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成, 之后才能将其它构造任务向上代理给父类的构造器
// 如上所述: 一个对象的内存只有在其所有存储属性确定之后才能完全初始化, 为了满足这一规则, 指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化
// 安全检查 2:
// 指定构造器必须先向上代理调用父类的构造器, 然后再为继承的属性设置新值, 如果没有这么做, 指定构造器赋予的新值将被父类中的构造器所覆盖
// 安全检查 3:
// 便利构造器必须先代理调用同一类中的其他构造器, 然后再为任意属性赋值, 如果没有这么做, 便利构造器赋予的新值将被一类中其它指定构造器所覆盖
// 安全检查 4:
// 构造器在第一阶段构造完后之前, 不能调用任何实例方法, 不能读取任何实例属性的值, 不能引用 self 作为一个值, 类实例在第一阶段结束以前并不是完全有效的, 只有第一阶段完成后, 该实例才会成为有效实例, 才能访问属性和调用方法
// 以下是 两段式构造过程中 基于上述安全检查的 构造流程展示
// 阶段 1:
// 某个指定构造器或便利构造器被调用
// 完成新实例内存的分配, 但此时内存还没有被初始化
// 指定构造器确保其所在类引入的所有存储型属性 都已赋初始值, 存储型属性所述的内存完成初始化
// 指定构造器调用父类的构造器, 完成父类属性的初始化
// 这个调用父类构造器的过程沿着 构造器链一直往上执行, 直到到大构造器链的最顶部
// 当到大构造器链的最顶部,且已确保所有势力包含的存储属性都已赋值, 这个实例的内存被认为已经完全初始化, 此时 阶段 1 完成
// 阶段 2
// 从顶部构造器链一直往下,每个构造器链中类的指定构造器都比有机会进一步定制实例, 构造器此时可以访问 self ,修改它的属性 并调用实例方法等等
// 最终, 任意构造器链中的便利构造器可以有机会定制实例和使用 self
// 构造器的继承和重写
// 跟 OC 中的 子类不同, Swift 中的子类默认情况下不会继承父类的构造器, Swift 的这种机制可以防止一个父类的简单构造器 被一个更精细的子类继承, 并错误地用来创建子类的实例
// 注意 : 父类的构造器仅会在适当的时候被继承
// 加入你希望自定义的子类能提供一个或者多个跟父类相同的构造器, 你可以在子类中提供这些构造器的自定义实现
// 当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器, 因此, 你必须在定义子类构造器时带上 override 修饰符, 即使你重写的是系统自动提供的默认构造器, 也需要带上 override 修饰符,
// 正如重写属性,方法或者是下标, override 修饰符会让编译器去检查父类中是否有相匹配的指定构造器, 并验证构造器参数是否正确
// 注意 : 当你重写一个父类的指定构造器是, 你总是需要重写 override 修饰符, 即使你的子类将父类的指定构造器重写为了便利构造器
// 相反,如果你编写一个 和 父类便利构造器 相匹配的子类构造器, 由于子类不能直接调用父类的便利构造器 , 因此, 严格意义上来讲, 你的子类并未对一个父类构造器提供重写, 最后的结果时, 你在子类中 '重写' 一个父类的便利构造器是, 不需要加 override
// 在下面的例子中定义了一个叫Vehicle的基类。基类中声明了一个存储型属性numberOfWheels,它是值为0的Int类型的存储型属性。numberOfWheels属性用于创建名为descrpiption的String类型的计算型属性:
class Vehicle{
var numberOfWheels = 0
var description: String{
return "\(numberOfWheels) wheel(s)"
}
}
// Vehicle 类只为存储属性提供默认值, 而不是自定义构造器, 因此, 它会自动获得一个默认构造器, 自动获得的 默认构造器 总会是 类中的指定构造器
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// 定义一个子类
class Bicycle: Vehicle{
override init() {
super.init()
numberOfWheels = 2
}
}
// 子类 Bicycle 定义了一个自定义指定构造器 init(), 这个指定构造器 和 父类的指定构造器相匹配, 所以 Bicycle 中的指定构造器需要带上 override 修饰符
// Bicycle 的构造器 init() 以调用 super.init() 方法开始, 这个方法的作用是调用 Bicycle 的父类 Vehicle 的 默认构造器,这样可以确保 Bicycle 在修改属性之前, 它所继承的属性 numberOfWheels 能被 Vehicle 类初始化, 在调用 super.init() 之后, 属性 numberOfWheels 的原值被新值 2 替换
let bicycle = Bicycle()
print("Bicyle: \(bicycle.description)")
// 注意 : 子类可以在初始化时修改继承来的变量属性, 但是不能修改继承来的常量属性
// 构造器的自动继承
// 如上所述, 子类在默认情况下不会继承父类的构造器, 但是如果满足特定条件, 父类构造器是可以被自动继承的. 在实践中, 这意味着对于许多常见场景你不必重写父类的构造器, 并且可以在安全的情况下以最小的代价继承父类的构造器
// 加入你为子类中引入的所有新属性都提供了默认值, 以下 2 个规则适用
// 1 : 如果子类没有定义任何指定构造器, 它将自动继承所有父类的指定构造器
// 2 : 如果子类提供了所有父类指定构造器的实现 -- 无论是通过规则 1 继承来的, 还是提供了自定义实现 -- 它将自动继承所有父类的 便利构造器
// 即使你在子类中添加了更多的便利构造器, 这两条规则仍然适用
// 注意 : 对于规则 2 . 子类可以将父类的指定构造器实现为便利构造器
// 指定构造器 和 便利构造器 实践
// 接下来的例子将在实践中展示 指定构造器, 便利构造器 以及构造器的自动继承,
// 类层次中的基类是 Food, 它是一个简单的用来封装食物名字的类, Food 类引入了一个叫做 name 的 String 类型的属性, 并且提供了两个构造器来创建 Food 实例
class Food{
var name: String
init(name: String) {
self.name = name
}
convenience init(){
self.init(name: "[Unnamed]")
}
}
// 类类型 没有默认的逐一成员构造器, 所以 Food 类提供一个接受单一参数 name 的指定构造器, 这个构造器可以使用一个特定的名字来创建新的 Food 实例:
let nameMeat = Food.init(name: "Bacon")
// Food 类中的构造器 init(name: String) 被定义为一个指定构造器, 因为它能确保 Food 实例的所有属性都能被初始化, Food 类没有父类, 所以 init(name: String) 构造器不需要调用 super.init() 来完后构造过程
// Food 类同样提供了一个没有参数的 便利构造器 init(), 这个 init() 构造器为新事物提供了 一个默认的占位名字, 通过横向代理 到指定构造器 init(name: String) 并给参数传值 [Unnamed] 来实现
let mysteryMeat = Food.init()
print(mysteryMeat.name)
// 类层级中第二个类是 Food 的子类 RecipeIngrdient , 这个类用来表示食物中的一项原料, 它 引入了 Int 类型的属性 quantity (以及从 Food 继承过来的 name 属性), 并且定义了两个构造器来创建
class RecipeIngredient: Food{
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
// RecipeIngredient 类拥有一个指定的构造器,init(name: String, quantity: Int) , 它可以用来填充参数 RecipeIngredient 实例的所有属性值 , 这个构造器已开始先将传入的 quantity 参数赋值给 quantity 属性, 这个属性也是 RecipeIngredient 唯一新引入的属性, 然后, 构造器向上代理到父类 Food 的 init(name: String),
// RecipeIngredient 还定义了一个便利构造器 init(name: String), 它值通过 name 来创建 RecipeIngredient 的实例
// 尽管 RecipeIngredient 将父类的指定构造器重写为了 便利构造器, 它依然提供了父类的所有指定 构造器的实现, 因此 ,RecipeIngredient 会自动继承父类的所有的便利构造器
// 类层级中第三个也是最后一个类 是 RecipeIngredient 的子类, 叫做 ShoppingListItem, 这个类构建了购物单中出现的某一种食谱原料
// 购物单中的每一项总是从未购买状态开始的。为了呈现这一事实,ShoppingListItem引入了一个布尔类型的属性purchased,它的默认值是false。ShoppingListItem还添加了一个计算型属性description,它提供了关于ShoppingListItem实例的一些文字描述:
class ShoppingListItem: RecipeIngredient{
var purchased = false
var description: String{
var output = "\(quantity) x \(name)"
output += purchased ? " ✔":" ✘"
return output
}
}
// 你可以使用全部三个继承来的构造器来 创建 ShoppingListItem 的新实例
var breakfaseList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem.init(name: "Eggs", quantity: 6)
]
breakfaseList[0].name = "Orange juice"
breakfaseList[0].purchased = true
for item in breakfaseList {
print(item.description)
}
// 如上所述, 例子中通过字面量方式创建了一个数组 breakfastList, 它包含了三个ShoppingListItem实例,因此数组的类型也能被自动推导为[ShoppingListItem]。在数组创建完之后,数组中第一个ShoppingListItem实例的名字从[Unnamed]更改为Orange juice,并标记为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。
// 可失败构造器
// 如果一个 类, 结构体, 或枚举类型的对象, 在构造过程中有可能失败, 则为其定义一个可失败的构造器, 这里所指的失败是, 如给构造器传入无效的参数值, 或缺少某种所需的外部资源, 又或者是不满足某种必要的资源
// 为了妥善处理这种情况, 你可以在一个类, 结构体 或是 枚举类型的定义中, 添加 一个或者多个可失败的构造器, 其语法 为 在 init 关键字后面加问好 (init?)
// 可失败的构造器的参数名和参数类型, 不能与其他非可失败构造器的参数名, 以及参数类型相同
// 可失败构造器会创建一个 类型为自身类型的 可选类型对象, 你通过 return nil 语句来表明可失败构造器在任何情况下应该 失败
// 注意 : 严格来说, 构造器都不支持返回值, 因为构造器本身的作用, 只是为了确保对象能被正确构造, 因此你只是用 return nil 表明可失败构造器构造失败, 而不要用关键字 return 来表明构造成功
// 下例中,定义一个名为 Animal 的结构体, 其中有一个名为 species 的 String 类型的常量属性. 同时该结构体还定义了一个接受一个名为 species 的 String 类型参数的可失败构造器, 这个可失败构造器检查传入的参数是否为一个空字符串, 如果是空字符串, 则构造失败, 否则,species 属性被赋值, 构造成功
struct Animal{
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}else{
self.species = species
}
}
}
// 检查一下
let someCreature = Animal.init(species: "Giraffe")
if let ssss = someCreature {
print(ssss.species)
}
let aaaaa = Animal.init(species: "")
if aaaaa == nil {
print("none")
}
// 注意 : 空字符串 ("") 和一个值 为 nil 的可选类型的字符串是两个完全不同的概念, 上栗中的空字符串 ("") 其实是一个有效的, 非可选类型的字符串
// 构造失败的传递
// 类, 结构体, 枚举 的可失败构造器 可以横向代理到类型中的其他可失败的构造器, 类似的, 子类的可失败构造器也能向上代理到父类的可失败构造器
// 无论是向上代理还是横向代理, 如果你代理到其他的可失败构造器触发构造失败, 整个构造过程立即终止, 接下来的任何构造代码不会再执行
// 注意 : 可失败构造器也可以代理到它的非可失败构造器, 通过这种方式, 你可以增加一个可能失败状态到现有的构造过程中
// 重写一个可失败构造器
// 如同其他的构造器, 你可以在子类中重写父类的可失败构造器, 或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器, 这使你可以定义一个不会构造失败的子类, 即使父类的构造器允许构造失败
// 注意 : 当你用子类的非可失败构造器重写父类的可失败构造器时, 向上代理到父类的可失败构造器的唯一方式是对䣂的可失败构造器的返回值进行强制解包
// 注意 : 你可用非可失败构造器重写可失败构造器, 反过来却不行
// 可失败构造器 init!
// 通常我们通过在 init 关键字后添加问好的方式 (init?) 来定义一个可失败构造器, 但你也可以通过在 init 后面添加惊叹号的方式来定义一个可失败构造器 (init?), 该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象
// 你可以在 init? 中代理到 init!, 反之亦然, 你可以用 init? 重写 init!, 反之亦然, 你还可以用 init 代理到 init!, 不过, 一旦 init! 构造失败, 则会触发一个断言
// 必要构造器
// 在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器
class SomeClass{
required init(){
}
}
// 在子类重写父类的必要构造器时, 必须在子类的构造器前 也添加 required 修饰符, 表明该构造器要求也应用于继承链后面的子类, 在重写父类的指定构造器时. 不需要添加 override 修饰符
// 注意 : 如果子类继承的构造器能满足必要构造器的要求, 则无须再子类中显式提供必要构造器的实现
// 通过 闭包 或函数设置属性的默认值
// 如果某个存储属性的默认值需要一些定制或设置, 你可以使用闭包或全局函数 为其提供定制的默认值, 每当某个属性所在类型的新实例被创建时, 对应的闭包或函数会被调用, 而他们的返回值会当做默认值赋值给这个属性
// 这中类型的闭包或函数通常会创建一个跟属性类型相同的临时变量, 然后修改它的值以满足逾期的初始状态, 最后返回的这个临时变量, 作为属性的默认值
class SomeClass2{
let someProperty: Int = {
var num = 0
num += 1
return num
}()
}
// 注意: 闭包结尾的大括号后面接了一对空的小括号, 这用来告诉 Swift 立即执行此闭包, 如果你忽略了这对括号, 相当于将闭包本身作为值赋给了属性, 而不是将闭包的返回值赋给了属性
// 注意: 如果你使用闭包来初始化属性, 请记住在闭包执行时,实例的其他部分都还没有初始化, 这意味着你不能在闭包里访问其他属性, 即使这些属性有默认值, 同样, 你也不能使用隐式的 self 属性, 或者调用任何实例方法