Swift 構(gòu)造過程

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è)):

  1. 指定構(gòu)造器必須保證它所在類引入的所有屬性都必須初始化完成,之后才能將其他構(gòu)造任務(wù)向上代理給父類的構(gòu)造器

    上面的Bicycle類就是先將自己類的brand屬性進(jìn)行初始化,之后再調(diào)用super.init向上代理父類的構(gòu)造器

  2. 指定構(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ī)則適用:

  1. 如果子類沒有指定任何構(gòu)造函數(shù),它將自動繼承所有父類的指定構(gòu)造函數(shù)
  2. 如果子類提供了所有父類指定構(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)用父類

那么我想重寫該怎么辦,重寫就有兩種情況:

  1. 父類是可失敗構(gòu)造函數(shù),子類重寫為非可失敗構(gòu)造函數(shù)
  2. 父類是非可失敗構(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í)例化后才可以修改類的屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,510評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容