Swift 構(gòu)造過程
step 1.
首先從Objective-C的構(gòu)造開始說起
#import <Foundation/Foundation.h>
@interface Object : NSObject
@end
#import "Object.h"
@implementation Object
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
@end
在Objective-C的構(gòu)造函數(shù)init中首先調(diào)用init,沿著繼承鏈一直往上走,知道到底繼承鏈的頂端,繼而進(jìn)行初始化繼承鏈逐個(gè)返回,如果繼承鏈中各類初始化沒錯(cuò)才能繼續(xù)進(jìn)行 Object的初始化
而Swift版本如下
import UIKit
class CustomObj: NSObject {
override init() {
// super.init() 隱式被調(diào)用
}
}
關(guān)于override,super.init() 隱式被調(diào)用稍后作分析。對比發(fā)現(xiàn)Swift中添加了override
其他的跟OC好像沒有不同。
step2.
往類中添加屬性
#import <Foundation/Foundation.h>
@interface Object : NSObject
@property (nonatomic, strong) NSString *property;
@end
#import "Object.h"
@implementation Object
- (instancetype)init {
if (self = [super init]) {
self.property = @""; // Situation 1. 初始化property
// self.property = @""; Situation 2. 不初始化 property
}
return self;
}
@end
你會發(fā)現(xiàn) 初始化property和不初始化property 在OC中均不會報(bào)錯(cuò),這是因?yàn)镺C中向 nil發(fā)送消息不會報(bào)錯(cuò)
import UIKit
class Object: NSObject {
let property: String
override init() {
property = "" // 注釋掉會報(bào)錯(cuò)
// property = "" Property 'self.property' not initialized at implicitly generated super.init call
}
}
Property 'self.property' not initialized at implicitly generated super.init call
self.property 在super.init 調(diào)用之前完成初始化,我們沒有調(diào)用super.init,但是錯(cuò)誤中明確指出在super.init調(diào)用的時(shí)候self.property 沒有初始化,這就是之前說的 隱式調(diào)用super.init
隱式調(diào)用 super.init
有一個(gè)非常重要的條件就是這個(gè)構(gòu)造方法時(shí) 指定初始方法,只有在指定初始化方法中才可以按著繼承鏈往上調(diào)用父類初始化,什么是指定初始化 ??
如果我想重新父類的屬性該怎么辦??這里引用 The Swift Programming Language 中的例子
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
class Bicycle: Vehicle {
var brand: String
override init() {
brand = "Brank" // 1.
super.init()
numberOfWheels = 2
}
}
這就是遵循Swift 4個(gè)安全檢查,確保不會出現(xiàn)錯(cuò)誤,具體的安全檢查如下(目前只用到兩個(gè)):
-
指定構(gòu)造器必須保證它所在類引入的所有屬性都必須初始化完成,之后才能將其他構(gòu)造任務(wù)向上代理給父類的構(gòu)造器
上面的Bicycle類就是先將自己類的brand屬性進(jìn)行初始化,之后再調(diào)用super.init向上代理父類的構(gòu)造器
指定構(gòu)造器必須先向上代理調(diào)用父類的構(gòu)造器,然后再為任意屬性賦新值,如果調(diào)用順序不是這樣,那么指定構(gòu)造器賦值的新值將會被父類構(gòu)造器所覆蓋
step4.
我們將上面的例子進(jìn)行改造
class Vehicle {
var numberOfWheels = 0
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
}
class Bicycle: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
// Error: Super.init isn't called on all paths before returning from initializer
}
}
從上面例子可以看到產(chǎn)生Error,那么我們嘗試解決:error是說父類構(gòu)造器在返回實(shí)例之前沒有被調(diào)用。唉我們之前不是說過有隱式調(diào)用嗎??那我們嘗試主動調(diào)用試試,代碼修改如下:
class Vehicle {
var numberOfWheels = 0
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
}
class Bicycle: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
super.init()// Error: Missing argument for parameter 'numberOfWheels' in call
}
}
有報(bào)錯(cuò),缺失參數(shù)numberOfWheels,,觀看我們Vehicle定義,定義指定構(gòu)造函數(shù)init(numberOfWheels: Int), 根據(jù)蘋果的說法,子類調(diào)用父類只能調(diào)用指定構(gòu)造函數(shù),即子類Bicycle只能調(diào)用init(numberOfWheels:), 那我們改之試試
class Vehicle {
var numberOfWheels = 0
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
}
class Bicycle: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
super.init(numberOfWheels: 2)
}
}
OK, 沒有Error
現(xiàn)在回到step4第一段代碼,為什么沒有super.init調(diào)用, 這里引入一個(gè)概念默認(rèn)構(gòu)造器,如果結(jié)構(gòu)體或類的所有屬性都有默認(rèn)值,同時(shí)沒有自定義的構(gòu)造器,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個(gè)默認(rèn)構(gòu)造器(default initializers)。這個(gè)默認(rèn)構(gòu)造器將簡單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為默認(rèn)值的實(shí)例。
第一個(gè)代碼中numberOfWheels有默認(rèn)值,所以Swift會創(chuàng)建一個(gè)默認(rèn)初始化init,但是再看默認(rèn)初始化化條件,是類沒有提供自定義構(gòu)造器,我們提供了,OK,那我們?nèi)サ粼僭囋嚕?/p>
class Vehicle {
var numberOfWheels = 0
}
class Bicycle: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
}
}
OK, 沒有問題
step5.
class Vehicle {
var numberOfWheels = 0
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
}
class Bicycle: Vehicle {
var brand: String
init(brand: String) {
self.brand = brand
super.init(numberOfWheels: 2)
}
override init(numberOfWheels: Int) {
self.brand = ""
super.init(numberOfWheels: numberOfWheels)
}
}
重寫指定構(gòu)造器需要加override
override
子類提供一個(gè)跟父類相同的構(gòu)造器,實(shí)際上是在重寫父類這個(gè)構(gòu)造器,因此必須帶上override
關(guān)鍵字,即使重寫父類自動提供的默認(rèn)構(gòu)造器,也同樣需要加上override
關(guān)鍵字
override
關(guān)鍵字會去檢查父類中是否有相匹配的指定構(gòu)造器,并驗(yàn)證構(gòu)造器參數(shù)是否正確
注意
當(dāng)你重寫一個(gè)父類的指定構(gòu)造器時(shí),你總是需要寫 override 修飾符,即使你的子類將父類的指定構(gòu)造器重寫為了便利構(gòu)造器。
重寫便利構(gòu)造器不需要加override
說了這么多,那什么是指定構(gòu)造器什么是便利構(gòu)造器呢??
在上面我們所寫的所有代碼中的init都是指定構(gòu)造器,指定構(gòu)造器的寫法如下:
init(params) {}
指定構(gòu)造器是類中最主要的構(gòu)造器,一個(gè)指定構(gòu)造器將初始類中所提供的所有屬性,并根據(jù)繼承鏈調(diào)用父類的初始化來實(shí)現(xiàn)父類的初始化
便利構(gòu)造器
convenience init(params){}
便利構(gòu)造器是類中比較次要,輔助的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個(gè)類中的指定構(gòu)造器,并為其提供參數(shù)
類的構(gòu)造代理規(guī)則:
規(guī)則 1
指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。
規(guī)則 2
便利構(gòu)造器必須調(diào)用同一類中定義的其它構(gòu)造器。
規(guī)則 3
便利構(gòu)造器必須最終導(dǎo)致一個(gè)指定構(gòu)造器被調(diào)用。
- 指定構(gòu)造器必須總是向上代理
- 便利構(gòu)造器必須總是橫向代理
接下來通過實(shí)例來探討下 指定構(gòu)造器 便利構(gòu)造器 ,實(shí)例來自于 蘋果語法指南
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unname]")
}
}
Foot類中包含一個(gè)name屬性,一個(gè)指定初始化方法 (init(name:))一個(gè)便利構(gòu)造器函數(shù)(convenience init()) ,便利構(gòu)造器調(diào)用指定初始化方法,類中所有的存儲屬性只能在定義或者指定初始化方法中賦值,驗(yàn)證下:
class Food {
var name: String
var size: Double
init(name: String) {
self.name = name // 1. Error: Return from initializer without initializing all stored properties
}
convenience init() {
self.size = 10.0 // 2. Error: Use of 'self' in property access 'size' before self.init initializes self
self.init(name: "[Unname]")
}
}
第一個(gè)Error:從初始化方法返回并沒有完成所有的存儲屬性初始化,這就是我們上面說的那個(gè)問題了;那第二個(gè)Error緣起何處??
原因是在便利構(gòu)造其中我們不負(fù)責(zé)類的創(chuàng)建工作,所以需要調(diào)用指定初始化方法來完成類的創(chuàng)建,但是上面的例子中我們是在調(diào)用初始化方法之前對size進(jìn)行設(shè)置,此時(shí)對象并未創(chuàng)建完成,所以錯(cuò),來,修改下:
class Food {
var name: String
var size: Double
init(name: String) {
self.name = name
size = 0
}
convenience init() {
self.init(name: "[Unname]")
self.size = 10.0
}
}
繼續(xù)我們上面的討論,接下來創(chuàng)建Food的子類:
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有幾個(gè)可調(diào)用的初始化方法,它聲明的兩個(gè)肯定可以使用:
let r1 = RecipeIngredient(name: "", quantity: 1)
let r2 = RecipeIngredient(name: "")
// 還有一個(gè)繼承來的 init()
let r3 = RecipeIngredient()
在Food類中init() 內(nèi)容調(diào)用 init(name:) 所以在子類中的構(gòu)造函數(shù)調(diào)用順序如下:
init() -> init(name:) ->init(name:, quantity:) -> super.init(name:)
這里有個(gè)注意點(diǎn),就是我們在重寫init(name:)時(shí)加override
,正如前面所說,當(dāng)重寫父類的指定初始化時(shí)需要加override
關(guān)鍵字,即使在這里這個(gè)指定構(gòu)造函數(shù)被重寫為變量構(gòu)造函數(shù),同樣到我們重寫便利構(gòu)造函數(shù)時(shí)變不需要添加override
關(guān)鍵字
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)
}
// 重寫便利構(gòu)造器
convenience init() {
self.init(name: "")
}
}
你同樣可以將父類的便利構(gòu)造函數(shù)重寫為指定構(gòu)造函數(shù)
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)
}
// 重寫便利構(gòu)造器為指定構(gòu)造函數(shù)
init() {
quantity = 1
super.init(name: "")
}
}
還有一點(diǎn)需要注意:子類可以在初始化時(shí)改變繼承來的變量屬性值,但不可以改變繼承來的常量屬性值
關(guān)于 “構(gòu)造器的自動繼承”
子類在默認(rèn)情況下不會繼承父類的構(gòu)造器,但如果滿足特定的條件父類的構(gòu)造器是可以被自動繼承的。
前提假設(shè)為子類中引入的所有新的屬性都提供了默認(rèn)值以下兩個(gè)規(guī)則適用:
- 如果子類沒有指定任何構(gòu)造函數(shù),它將自動繼承所有父類的指定構(gòu)造函數(shù)
- 如果子類提供了所有父類指定構(gòu)造函數(shù),無論是通過1.繼承來的還是提供了自定義實(shí)現(xiàn)它將自動繼承父類所有的便利構(gòu)造器。
上面Food那個(gè)例子也能看出這兩條規(guī)則,我們重寫了父類的指定的構(gòu)造函數(shù),那么我們同樣繼承了init個(gè)init(name:)兩個(gè)便利構(gòu)造函數(shù)
再添加一個(gè)類更深刻地了解下這兩個(gè)規(guī)則:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? "?" : "?"
return output
}
}
由于它為自己引入的所有屬性都提供了默認(rèn)值,并且自己沒有定義任何構(gòu)造器ShoppingListItem 將自動繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器
step6
如果一個(gè)類,結(jié)構(gòu)體,或者枚舉類型的對象,在構(gòu)造過程中可能失敗,則為其定義一個(gè)可失敗的構(gòu)造器
可失敗構(gòu)造函數(shù)的參數(shù)名跟類型不能與其他非可失敗構(gòu)造函數(shù)的參數(shù)名,及其參數(shù)類型相同
struct Animal {
let speices: String
init?(speices: String) {
if speices.isEmpty {
return nil
}
self.speices = speices
}
}
可失敗構(gòu)造類型返回的類型為Optional類型
// 可以通過 guard來使用創(chuàng)建的Animal
guard let animal = Animal(speices: "Giraffe") {
return
}
// 如果給 Animal傳遞一個(gè)空字符串,那么Animal創(chuàng)建失敗,返回nil
我們通過一個(gè)例子來看下構(gòu)造失敗的傳遞
class Product {
let name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 {
return nil
}
self.quantity = quantity
super.init(name: name)
}
}
上面就是可失敗的構(gòu)造函數(shù)的傳遞。就是子類調(diào)用父類
那么我想重寫該怎么辦,重寫就有兩種情況:
- 父類是可失敗構(gòu)造函數(shù),子類重寫為非可失敗構(gòu)造函數(shù)
- 父類是非可失敗構(gòu)造函數(shù),子類重寫為可失敗構(gòu)造函數(shù)
我們來挨個(gè)驗(yàn)證下:
class Product {
let name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 {
return nil
}
self.quantity = quantity
super.init(name: name)
}
override init(name: String) {
quantity = 1
super.init(name: name)!
}
}
OK, 沒有問題
class Product {
let name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
init() {
name = ""
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 {
return nil
}
self.quantity = quantity
super.init(name: name)
}
override init(name: String) {
quantity = 1
super.init(name: name)!
}
override init?() { // Error: Failable initializer 'init()' cannot override a non-failable initializer
}
}
出現(xiàn)問題Error:非可失敗構(gòu)造器不能被可失敗構(gòu)造器重寫;
step7
require 構(gòu)造函數(shù),當(dāng)一個(gè)類添加require 關(guān)鍵字時(shí),子類都必須實(shí)現(xiàn)該構(gòu)造器,在重寫require構(gòu)造函數(shù)時(shí),不需要添加override
class SomeClass {
required init() {
// 構(gòu)造器的實(shí)現(xiàn)代碼
}
}
class SomeSubclass: SomeClass {
required init() {
// 構(gòu)造器的實(shí)現(xiàn)代碼
}
}
總結(jié):
初始化注意好指定構(gòu)造函數(shù)與便利構(gòu)造函數(shù)區(qū)別就好,指定初始化中對類進(jìn)行實(shí)例化,所以要在這里面進(jìn)行屬性賦值調(diào)用父類指定初始化方法的操作。而便利構(gòu)造函數(shù)只是方便外部調(diào)用,并不賦值實(shí)例初始化,便利初始化要調(diào)用類的指定初始化來完成類的實(shí)例化,實(shí)例化后才可以修改類的屬性