Swift init方法詳解

Swift的初始化方法是為 類、結(jié)構(gòu)體、枚舉使用的,它能為每個(gè)存儲(chǔ)屬性提供初始值,Swift的初始化方法和OC的初始化方法不同,Swift的初始化方法不需要返回一個(gè)值(對(duì)象)。它的主要任務(wù)是在該類的實(shí)例對(duì)象使用前充分地初始化該對(duì)象。
當(dāng)然了,類對(duì)象一樣可以實(shí)現(xiàn)初始化方法。

1、初始化方法的一般形式

init(參數(shù)標(biāo)簽 參數(shù)名 : 參數(shù)類型) {
    //init code
}

2、初始化方法必須要為每一個(gè)存儲(chǔ)屬性提供初始值

2.1 下面的Vehicle類聲明了兩個(gè)存儲(chǔ)屬性:wheelCounts和color,我們?cè)跇?gòu)造初始化方法的時(shí)候,必須要初始化這兩個(gè)存儲(chǔ)屬性;如果缺少任何一個(gè)存儲(chǔ)屬性的初始化,都會(huì)報(bào)錯(cuò)!

class Vehicle : NSObject {
    var wheelCounts : Int
    var color : UIColor
            
    init(wheelCounts wheels : Int, vehicleColor color : UIColor) {
        self.wheelCounts = wheels
        self.color = color
    }
}

//調(diào)用
var vehicle = Vehicle.init(wheelCounts: 2, vehicleColor: UIColor.white)

2.2 我們?yōu)?code>Vehicle類增加一個(gè)可選型屬性price,其它代碼不變,由于可選型默認(rèn)是被初始化為nil的,所以我們不需要特意初始化可選型屬性;

class Vehicle : NSObject {
    var wheelCounts : Int
    var color : UIColor
    var price : Double?
            
    init(wheelCounts wheels : Int, vehicleColor color : UIColor) {
        self.wheelCounts = wheels
        self.color = color
    }
}

//調(diào)用
var vehicle = Vehicle.init(wheelCounts: 2, vehicleColor: UIColor.white)

3、值類型(枚舉、結(jié)構(gòu)體)初始化委托

初始化委托:一個(gè)初始化方法可以調(diào)用其它初始化方法來(lái)實(shí)現(xiàn)屬性的初始化;
這樣做的好處是:避免重復(fù)的初始化代碼

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        //調(diào)用第2種初始化方法
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

上面有3種初始化方法,init(),init(origin: Point, size: Size)以及init(center: Point, size: Size);
顯然,init(center: Point, size: Size)是通過(guò)調(diào)用init(origin: Point, size: Size)來(lái)實(shí)現(xiàn)初始化的,這就是初始化委托

4、類的繼承和初始化

4.1 特定初始化和便利初始化

特定初始化方法是一個(gè)類的主要初始化方法,特定初始化方法能夠初始化該類引入的所有屬性,并且調(diào)用父類的初始化方法,使得初始化鏈能夠繼續(xù)像上(父類)進(jìn)行;
每個(gè)類至少要有一個(gè)特定初始化方法,某些情況下,可以從父類繼承一個(gè)或多個(gè)初始化方法;

便利初始化方法是次要的,你可以定義一個(gè)便利初始化方法,在這個(gè)方法里,必須要調(diào)用本類的某個(gè)特定初始化方法;在調(diào)用本類的特定初始化方法的過(guò)程中,既可以使得初始化鏈繼續(xù)向父類進(jìn)行,又能給那些在特定初始化方法中初始化的屬性一個(gè)默認(rèn)值,這樣我們就不需要再寫代碼對(duì)這部分屬性進(jìn)行賦值了,可以減少重復(fù)代碼。
當(dāng)然了,遍歷初始化方法并不是必要的。

4.2 初始化方法的語(yǔ)法

特定初始化方法的語(yǔ)法

init(parameters) {
    statements
}

遍歷初始化方法的語(yǔ)法

convenience init(parameters) {
    statements
}

4.3 類的初始化代理

為了簡(jiǎn)化特定初始化方法和便利初始化方法的關(guān)系,Swift聲明了3條規(guī)則

  1. 一個(gè)類的 特定初始化方法 必須要調(diào)用其父類的 特定初始化方法

  2. 便利初始化方法必須要調(diào)用該類的另一個(gè)始化方法

  3. 便利初始化方法的最后必須要調(diào)用該類的某一個(gè)特定初始化方法

解讀:
規(guī)則1是為了使得子類的初始化方法能夠向上委托,能實(shí)現(xiàn)其父類甚至超類的屬性初始化;
規(guī)則2表示便利初始化方法內(nèi)必須要調(diào)用該類的另一個(gè)初始化方法(可以是特定初始化方法,也可以是便利初始化方法);
規(guī)則3則是說(shuō)便利初始化方法的最后必須要調(diào)用一個(gè)特定初始化方法,這和規(guī)則2看起來(lái)有點(diǎn)沖突?
其實(shí)并不沖突,規(guī)則2要求每個(gè)便利初始化方法都要調(diào)用該類的另一個(gè)初始化方法,即使它調(diào)用的是另一個(gè)便利初始化方法,但是這個(gè)被調(diào)用的便利初始化方法肯定還是會(huì)間接或者直接調(diào)用某一個(gè)特定初始化方法的;

簡(jiǎn)單來(lái)說(shuō)就是:

  • 特定初始化方法是向上委托
  • 便利初始化方法總是橫向委托


    image.png

上圖中的父類有1個(gè)特定初始化方法和2個(gè)便利初始化方法,便利初始化方法1調(diào)用了特定初始化方法,另一個(gè)便利初始化方法,調(diào)用了便利初始化方法1,最終,還是調(diào)用了特定初始化方法。
子類則包含2個(gè)特定初始化方法和1個(gè)便利初始化方法,2個(gè)特定初始化方法都是繼承自父類唯一的特定初始化方法的;子類的便利初始化方法則還是橫向調(diào)用本類的特定初始化方法,而不是直接繼承自父類的便利初始化方法。

4.4 2段初始化

Swift中的類的初始化有2個(gè)階段
階段1:每個(gè)存儲(chǔ)屬性必須要被初始化且分配值
階段2:每個(gè)類的實(shí)例對(duì)象在被使用之前都有機(jī)會(huì)進(jìn)一步定制其存儲(chǔ)屬性

這兩個(gè)階段為的是防止屬性在初始化之前就被訪問(wèn)了,也可以防止屬性值被另一個(gè)初始化程序意外地設(shè)置為不同的值。
Swift編譯器執(zhí)行了4次有用的安全檢查,以確保兩階段初始化安全無(wú)誤。
1、在特定初始化方法中,該類引入的所有屬性被初始化之后,才能調(diào)用父類的初始化方法

原因:一個(gè)類的實(shí)例對(duì)象,只有當(dāng)它的所有屬性被初始化之后,才能認(rèn)為該對(duì)象被完全初始化;所以在子類中引入的屬性必須要先被初始化賦值,然后才能使初始化鏈向上委托(調(diào)用super.init(***) )

2、在特定初始化方法中,必須要先調(diào)用父類的初始化方法,然后才能對(duì)繼承來(lái)的屬性進(jìn)行賦值

原因:如果先對(duì)繼承來(lái)的屬性進(jìn)行初始化賦值,再調(diào)用父類的初始化方法,可能會(huì)改變從父類繼承來(lái)的屬性的值

3、在便利初始化方法中,必須要先調(diào)用該類的另一個(gè)初始化方法,然后再為其屬性賦值

原因:如果我們先對(duì)屬性進(jìn)行初始化賦值,再調(diào)用另一個(gè)初始化方法,有可能會(huì)導(dǎo)致已經(jīng)被初始化的屬性的值被修改了

4、任何一個(gè)初始化方法中,必須要先完成第一階段的初始化工作,才能使用self關(guān)鍵字、調(diào)用實(shí)例方法、讀取屬性的值。

下面說(shuō)明了兩個(gè)階段的工作已經(jīng)完成的標(biāo)志
階段1:

  • 一個(gè)特定初始化方法或者便利初始化方法被調(diào)用
  • 類的實(shí)例對(duì)象的內(nèi)存已經(jīng)被分配,但是還沒(méi)被初始化
  • 該類的一個(gè)特定初始化方法保證該類引入的存儲(chǔ)屬性都有值了,這些存儲(chǔ)屬性就被初始化了
  • 特定初始化方法向上委托,調(diào)用父類的初始化方法,以確保父類引入的存儲(chǔ)屬性被初始化賦值
  • 繼續(xù)向上委托,直到繼承鏈的頂端
  • 直到繼承鏈的頂端,且該繼承鏈最上面的類確保它引入的存儲(chǔ)屬性都被初始化了,則該實(shí)例對(duì)象的的內(nèi)存完全被初始化了,階段1結(jié)束

階段2:

  • 從繼承鏈的頂部向下,每一個(gè)類的特定初始化方法都能夠進(jìn)一步自定義這個(gè)實(shí)例對(duì)象?,F(xiàn)在可在初始化方法中訪問(wèn)self、修改它的屬性、調(diào)用它的實(shí)例方法等
  • 最終,繼承鏈里的每個(gè)便利初始化方法都能自定義實(shí)例對(duì)象和使用self關(guān)鍵字

下面展示一下階段1


階段1.png

在這個(gè)例子中,從調(diào)用子類的便利初始化方法開(kāi)始,便利輸入法橫向委托,,調(diào)用本類的特定初始化方法。
這個(gè)特定初始化方法確保子類引入的存儲(chǔ)屬性都被初始化賦值了,然后向上委托,調(diào)用父類的特定初始化方法。
父類的特定初始化方法確保父類引入的所有存儲(chǔ)屬性被初始化賦值了;這里沒(méi)有更上一層的父類了,到此為止;
當(dāng)父類所有的屬性都有值了,第一階段就完成了。

下面展示一下階段2


階段2.png

首先,階段2是從上往下的。父類的特定初始化方法有機(jī)會(huì)去自定義實(shí)例對(duì)象;當(dāng)父類的特定初始化方法結(jié)束了,子類的特定初始化方法可以去自定義對(duì)象;
最終,一旦子類的特定初始化方法結(jié)束了,便利初始化方法就可以開(kāi)始自定義。

5、初始化方法的繼承和重寫

Swift并不會(huì)像OC那樣自動(dòng)繼承父類的初始化方法,因?yàn)槿绻割惖囊粋€(gè)較為簡(jiǎn)單的初始化方法被更加復(fù)雜多變的子類自動(dòng)繼承了,有可能會(huì)導(dǎo)致子類的實(shí)例對(duì)象初始化不完全。

但是有些時(shí)候,子類是會(huì)自動(dòng)繼承父類的初始化方法的。

在給子類寫初始化方法時(shí),如果匹配了父類的某個(gè) 特定初始化方法,那就要在子類的該初始化方法前加上修飾詞override,即便是重寫默認(rèn)自動(dòng)生成的初始化方法時(shí)也需要加上override修飾詞。

就像重寫屬性、方法或者下標(biāo),override關(guān)鍵詞提示Swift去檢查父類是否有匹配的特定初始化方法,并且驗(yàn)證被重寫的初始化方法的參數(shù)是否像預(yù)期的一樣被指定好。

在子類中,不管我們是直接重寫父類的特定初始化方法,還是子類的便利初始化方法重寫父類的特定初始化方法,我們都需要使用override關(guān)鍵字

相反的,如果你在子類中重寫的初始化方法匹配到的是父類的便利初始化方法,則父類的便利初始化方法永遠(yuǎn)不會(huì)被調(diào)用,因?yàn)槲覀冎溃割惖谋憷跏蓟椒ㄊ菬o(wú)法被重寫的,只有特定初始化方法才能被重寫。

看個(gè)例子,我們?cè)谶@里聲明了一個(gè)Vehicle類,它包含一個(gè)自定義的特定初始化方法和一個(gè)便利初始化方法

    class Vehicle {
            var wheelCounts : Int
            var color : UIColor
            var price : Double?
    
            //自定義的特定初始化方法
            init(wheelCounts wheels : Int) {
                self.wheelCounts = wheels
                self.color = UIColor.white
            }
            //便利初始化方法
            convenience init(color : UIColor) {
                self.init(wheelCounts : 0)
                self.color = color
            }
            
        }

再定義一個(gè)Car類,繼承自上面的Vehicle類


Car類.png

我們發(fā)現(xiàn),子類定義的便利初始化方法是可以匹配到父類的特定初始化方法的,但是在該便利初始化方法中,還是必須要調(diào)用本類的特定初始化方法;這么一看,似乎子類的便利初始化方法和父類的特定初始化方法沒(méi)什么關(guān)系,只是方法名和參數(shù)相同而已;
但是如果子類的初始化方法匹配到的是父類的便利初始化方法,那么就會(huì)報(bào)錯(cuò):
子類的初始化方法沒(méi)有重寫父類的特定初始化方法。

現(xiàn)在我們可以整理一下上面的所有信息
在子類的特定初始化方法中,要先初始化子類新引入的存儲(chǔ)屬性,然后調(diào)用父類的某個(gè)特定初始化方法(不能是父類的便利初始化方法),最后才能修改從父類繼承下來(lái)的屬性的值。

init(參數(shù)名 : 參數(shù)類型) {
    初始化本類新引入的存儲(chǔ)屬性
    super.init(參數(shù)1, 參數(shù)2)  //調(diào)用父類的某個(gè)特定初始化方法
    修改從父類繼承來(lái)的屬性的值
}
  • 子類的特定初始化方法必須要調(diào)用父類的特定初始化方法,不可以調(diào)用父類的便利初始化方法
  • 子類可以重寫父類的特定初始化方法,不能重寫父類的便利初始化方法
  • 子類的便利初始化方法,必須要調(diào)用本類的特定初始化方法
  • 子類的便利初始化方法可以重寫(或者說(shuō)匹配)父類的特定初始化方法,但是依然需要調(diào)用本類的某個(gè)特定初始化方法

6、可失敗的初始化方法

有時(shí)候我們需要定義一個(gè)可失敗的類、結(jié)構(gòu)體或者枚舉的初始化方法;失敗的情況可能由無(wú)效的初始化參數(shù)值、缺少所需的外部資源或者一些其它阻止初始化成功的情況觸發(fā)。
比如:人的頭發(fā)數(shù)量比如大于等于一,如果傳進(jìn)來(lái)的參數(shù)是負(fù)值,那就可以觸發(fā)初始化失敗。

1、可以定義多個(gè)可失敗的初始化方法
2、不能同時(shí)定義可失敗的初始化方法和不可失敗的初始化方法

可失敗的初始化方法創(chuàng)造了一個(gè)初始化對(duì)象的可選型,也就是說(shuō)這個(gè)正在初始化的對(duì)象是可以為nil的,所以,當(dāng)我們需要觸發(fā)失敗的時(shí)候,我們可以通過(guò)return nil來(lái)觸發(fā)。

嚴(yán)格來(lái)說(shuō),初始化方法不需要返回值,初始化方法是為了讓初始化對(duì)象能夠完全地、正確無(wú)誤地完成初始化。所以即使你返回了一個(gè)nil來(lái)觸發(fā)失敗,這并不表明初始化成功了。
語(yǔ)法:

init?(參數(shù)名 :參數(shù)類型) {
    if(條件) {return nil}
    //條件通過(guò)執(zhí)行下面的代碼,即表示可繼續(xù)初始化
    //code...
}

例子:


返回值是對(duì)象的可選型.png

7、枚舉的可失敗初始化方法

        //枚舉的可失敗初始化方法
        enum Fruits {
            case Banana
            case Oriange

            init?(by fruitName : String) {
                switch fruitName {
                case "banana" :
                    self = .Banana
                case "oriange" :
                    self = .Oriange
                default:
                    return nil
                }
            }
        }
        var fruit = Fruits.init(by: "Apple")
        if fruit == nil {
            print("Initialize fruit failed!")
        }

或者如果枚舉有rawValue,也可以根據(jù)rawValue字段判斷是否能夠初始化成功

        enum Foods : Character {
            case cake = "c"
            case meat = "m"
        }
        let food = Foods.init(rawValue: "a")
        if food == nil {
            print("Initialize food failed!")
        }

7.2 可失敗初始化方法的重寫

可以在子類用不可失敗初始化方法來(lái)重寫父類的可失敗初始化方法

class Document {
    var name: String?
    init() {}
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

子類用一個(gè)不可失敗的初始化方法重寫了父類的init?(name: String)方法

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

8、必須要被繼承的初始化方法

如果我們要求每一個(gè)子類都要重寫父類的某個(gè)初始化方法,可以在該初始化方法前加上關(guān)鍵詞required;同時(shí),子類重寫該方法時(shí),必須要在初始化方法前加上required關(guān)鍵詞,不需要寫override關(guān)鍵詞

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}
class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

另外,如果程序員不構(gòu)造初始化方法,結(jié)構(gòu)體也有自動(dòng)生成的初始化方法

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

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