Swift initializer

今天聊聊Swift中的構(gòu)造器,無論你是Swfit新手還是混跡論壇多年的老鳥,相信你看完本文之后一定會有所收獲。

我們將討論以下四部分內(nèi)容:

  • designated initializer
  • convenience initializer
  • required 關(guān)鍵字
  • protocol中的initializer

Designated Initializer

當我們創(chuàng)建一個class的時候,如果這個class擁有****未初始化的非Optional屬性****,則必須提供一個構(gòu)造器(稱為designated initializer)。這句話有兩個重點:
****1.未初始化;2.非Optional屬性。****
比較一下下面幾種寫法:

//A
class Person {
    let name : String = ""
}
//B
class Person {
    let name : String
}
//C
class Person {
    var name : String?
}
//D
class Person {
    let name : String?
}

A,C都可以正常編譯,B,D會報錯(error: class 'Person' has no initializers)。因為Swift不允許非Optional變量在沒有初始化的情況下使用或者訪問,這樣會造成潛在的風險,于是Swift編譯器對構(gòu)造器做了嚴格的限制:類必須提供一個designated initializer,并且初始化所有****未初始化的非Optional屬性****


A,B兩種寫法中,let換成var結(jié)果是一樣的。但是D比較特殊,在Swift中,常量在訪問和使用之前必須初始化(哪怕是Optional)

var p : String?
print(p)
//正常編譯
let p : String?
print(p)
//報錯:error: constant 'p' used before being initialized

由此可見我們上面的描述并不準確,應當是****未初始化的非Optional屬性或Optinal常量****。不過一般我們不會聲明一個Optional常量,另外這么說也是在太過拗口,所以就簡單稱之為****未初始化的非Optional屬性****吧(下文也會這樣表述)。

同樣,在子類初始化過程中,Swift也做了嚴格的限制

class Person {
    let name : String
    init(name : String){
        self.name = name
    }
}
class Engineer: Person {
    var title : String
    init(name: String, title: String) {
        self.title = title
        super.init(name: name)
    }
}

****首先****,Engineer必須調(diào)用super.init,如果你這樣寫:

    init(name: String, title: String) {
        self.title = title
        self.name = name
    }

不好意思,self.name是常量,不可修改。即便我們將let name : String改為var name : String,仍會報錯,而且是兩個:

  • error: super.init isn't called on all paths before returning from initializer
  • error: use of 'self' in property access 'name' before super.init initializes self

從這兩個錯誤信息可以看出,在子類的構(gòu)造器中,父類的成員變量必須通過父類的designated initializer進行初始化。也就是說,我們必須調(diào)用super.init(第一個error),而且在調(diào)用super.init之前不能使用和訪問任何父類的成員變量(第二個error)。

****其次****,super.init必須在子類所有****未初始化的非Optional屬性****初始化之后調(diào)用。


這跟我們以前所了解的不同,例如在JAVA的構(gòu)造器中我們會先調(diào)用super.init。Swift之所以這樣做,同樣是為了安全性。
假設(shè)我們要求在初始化一個Person之后,立刻輸出一些信息用來debug:

class Person {
    var name : String
    init(name : String){
        self.name = name
        printDescription()
    }
    
    func printDescription() -> () {
        print("name=\(name)")
    }
}
class Engineer: Person {
    var title : String
    init(name: String, title: String) {
        self.title = title
        super.init(name: name)
    }
    
    override func printDescription() -> () {
        print("name=\(name), title=\(title)")
    }
}

如果在Engineerinit中,我們首先調(diào)用super.init,那么我們就會在初始化title之前執(zhí)行子類的printDescription方法,這是Swift所不允許的。


Convenience Initializer

一個類可以提供多個designated initializer,但是designated initializer不可以調(diào)用designated initializer。
如果需要提供一個依賴designated initializer的構(gòu)造器,需要添加關(guān)鍵字convenience,稱為convenience initializer。
在上面的例子中,假設(shè)我們從文件中讀取人的信息,讀取到的是****“名字;職位;聯(lián)系方式”****格式的字符串。我們需要寫一個構(gòu)造器方法從這串文字中解析出相應的信息。

class Person {
    let name : String
    init(name : String){
        self.name = name
    }
    
    convenience init(convertFromLiteral value : String){
        var name = ""
        let components = value.componentsSeparatedByString(";")
        for component in components{
            if(name.characters.count == 0){
                name = component
                break
            }
        }
        self.init(name: name)
    }
}

因為新的構(gòu)造器調(diào)用了self.init,因此需要添加關(guān)鍵字convenience,否則會報錯:error: designated initializer for 'Person' cannot delegate (with 'self.init'); did you mean this to be a convenience initializer?

一個類可以擁有多個convenience initializer,而且convenience initializer可以調(diào)用其他convenience initializer。
convenience initializer有以下約束:

  • 必須調(diào)用當前類的designated initializer,調(diào)用父類的designated initializer是不行的。
  • 只有當前類可以使用(子類也可以使用,不過需要特殊處理。見下文)。

Required 關(guān)鍵字

如果父類中的構(gòu)造器需要在子類中重寫,可以使用required關(guān)鍵字。子類必須重寫父類中標記為required的構(gòu)造器方法(不管是designated initializer還是convenience initializer)。
上面的例子中,init(convertFromLiteral value : String)并不適合子類,所以我們加上required關(guān)鍵字,編譯器會提示我們Engineer沒有實現(xiàn)required initializer。

class Engineer: Person {
    var title : String
    
    init(name: String, title: String) {
        self.title = title
        super.init(name: name)
    }
    
    required convenience init(convertFromLiteral value : String){
        var name = ""
        var title = ""
        let components = value.componentsSeparatedByString(";")
        for component in components{
            if(name.characters.count == 0){
                name = component
            }else if(title.characters.count == 0){
                title = component
                break
            }
        }
        self.init(name: name, title: title)
    }
}

上文提到了convenience initializer只有當前類才能使用,Swift不允許子類通過父類構(gòu)造器進行初始化是顯而易見的。convenience initializer實際上更像一個方法,如果我們將某個convenience initializer所依賴的designated initializer標記為required,就相當于告訴了編譯器我們的子類一定重寫了這個designated initializer。也就是說,如果我們的子類通過這個convenience initializer進行初始化,最終會執(zhí)行子類中重寫的designated initializer。

說這么一大堆,用代碼看的更清楚:

class Person {
    let name : String
    required init(name : String){
        self.name = name
    }
    
    convenience init(convertFromLiteral value : String){
        var name = ""
        let components = value.componentsSeparatedByString(";")
        for component in components{
            if(name.characters.count == 0){
                name = component
                break
            }
        }
        self.init(name: name)
    }
}

class Engineer: Person {
    var title : String
    
    required init(name: String) {
        self.title = "unknow"
        super.init(name: name)
    }
    
    init(name: String, title: String) {
        self.title = title
        super.init(name: name)
    }
}

let peter = Person.init(convertFromLiteral: "peter;")
print(peter.name)
let tom = Engineer.init(convertFromLiteral: "tom;")
print(tom.name + ", " + tom.title)

將父類中convenience initializer所依賴的designated initializer標記為required,我們就可以通過該convenience initializer初始化子類了。


Protocol中的Initializer

Swift提供了很多包涵構(gòu)造器的protocol,如StringLiteralConvertible,DictionaryLiteralConvertibleArrayLiteralConvertible等。

如果我們想通過如下方式初始化,我們就需要用到StringLiteralConvertible

let peter : Person = "Peter"

代碼如下:

class Person: StringLiteralConvertible{
    let name: String
    init(name value: String) {
        self.name = value
    }
    
    required convenience init(stringLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
    }
}

let peter : Person = "Peter"

是不是很cool?

需要注意的是,Swfit很好的控制了構(gòu)造器的作用域(默認情況下,convenience initializer是子類不可見的),但是protocol中的方法是子類可見的。所以protocol中的構(gòu)造器方法都必須聲明為required
如果實現(xiàn)protocol的是一個final類,那么則沒必要再是required,因為它不會有子類。


等一下,如果Person是第三方庫中的類呢?
你可能會說沒問題啊,我們可以通過extension實現(xiàn)StringLiteralConvertible

class Person: StringLiteralConvertible{
    let name: String
    init(name value: String) {
        self.name = value
    }
    
}

extension Person: StringLiteralConvertible{
    required convenience init(stringLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
    }
    
    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
    }
}

let peter : Person = "Peter"

真是這樣嗎?Sorry!!!

  • 首先,required initializer必須在類中聲明。所以上面的required關(guān)鍵字要不得
  • 其次,protocol中的構(gòu)造器方法必須聲明為required,除非Personfinal的。

emmm....

What Else

structenum同樣可以擁有構(gòu)造器,不過上面提到的designated initializer,convenience initializer,required 關(guān)鍵字都是針對class的,不要搞混。
最后上段代碼,供大家把玩

enum Sex{
    case Male
    case Female
    case Unknow
}
let malesName = ["peter", "tom"]
let femalesName = ["marry", "taylor"]
extension Sex: StringLiteralConvertible{
    init(stringLiteral value: String) {
        if(malesName.contains(value)){
            self = .Male
        }else if(femalesName.contains(value)){
            self = .Female
        }else{
            self = .Unknow
        }
        print(self)
    }
    init(extendedGraphemeClusterLiteral value: String) {
        self.init(stringLiteral: value)
    }
    init(unicodeScalarLiteral value: String) {
        self.init(stringLiteral: value)
    }
}

var sex : Sex = "tom"
sex = "marry"
sex = "gollum"
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:http://huizhao.win/2016/11/13/swift-init/ 從 Objectiv...
    趙大老板閱讀 35,047評論 11 121
  • 簡介 *自定義構(gòu)造過程 *默認構(gòu)造器 *值類型的構(gòu)造器代理 *類的繼承和構(gòu)造過程 *可失敗構(gòu)造器 *必需構(gòu)造器 *...
    FishSha閱讀 345評論 0 0
  • 適當距離 產(chǎn)生驚喜 今天他好像對我特別好呀,不知道是這兩天我不在伙食太差突然一下伙食好了感動的,還是太想我了,總之...
    fangyuanjili閱讀 241評論 0 0
  • 日念親人一處好,加持美好享幸福! 第11天(2017.8.9) 1.念老公好:上午繼續(xù)去婆婆家?guī)兔?,中午回來后下?..
    軒軒媽慧子閱讀 295評論 0 0
  • 今天有人來傾訴,不久前剛做的一個決定,發(fā)現(xiàn)并不適合自己,或者說跟當初設(shè)想的有很多不同。此時,恰好有另外一個機會,在...
    理想家園閱讀 137評論 0 4

友情鏈接更多精彩內(nèi)容