iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

今天我們繼續初始化器,真的是有夠長的。

可失敗初始化器

定義一個初始化器是可能會失敗的 Class、Struct、Enum 是很有用的。這邊指的失敗是,如果初始化器傳入無效的參數,或者去少所需的條件,或者條件沒有達到等狀況。

而這種可失敗的初始化器,可以在一個 Class、Struct、Enum 的定義中可以新增一個或者多個。語法根 init 一樣,只是在後面多問號 init? 。

可失敗初始化器會創建一個類型為自身類型可選類型的對象。可以通過 return nil 來表明可失敗初始化器在何種情況下應該會失敗

讓我們來看一下官方的例子

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// 打印“12345.0 conversion to Int maintains value of 12345”

let valueChanged = Int(exactly: pi)
// valueChanged 是 Int? 類型,不是 Int 類型

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// 打印“3.14159 conversion to Int does not maintain value”

上面的可失敗初始化器 init(exactly:) 在確保數字轉換的時候可以保有原始數值,如果不行則 nil 。
所以在 pi 時他如果為 nil 則 print 無法保有原始值。

接下來來看 Struct 的例子,官方這邊定義了一個 Animal 的 Struct 。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
        	return nil
        }
        self.species = species
    }
}

裡面定義了如果是空字串則失敗。不然就初始化成功。

可以通過實例來檢查是否設置成功。

let someCreature = Animal(species: "Giraffe")

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印“An animal was initialized with a species of Giraffe”

如果是空的

let anonymousCreature = Animal(species: "")

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// 打印“The anonymous creature could not be initialized”

Enum 的可失敗初始化器

可以使用 Enum 來建構可失敗初始化器,只要沒有匹配任何 Enum 的成員就會初始化失敗。

官方例子裡定義了 TemperatureUnit 的 Enum 。有三個狀態 Kelvin, Celsius, Fahrenheit 。

enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

可以實例化來測試有沒有設置正確。

let fahrenheitUnit = TemperatureUnit(symbol: "F")

if fahrenheitUnit != nil {
    print("succeeded")
    // succeeded
}

let unknownUnit = TemperatureUnit(stmbol: "X")

if unknownUnit == nil {
    print("failed")
    // failed
}

X 不包含在 Enum 裡面所以會 Failed 。

帶有原始值的 Enum 的可失敗初始化器

可以設置有帶原始值的 Enum 來建構可失敗初始化器,而跟上面的例子差不多,所以我們可以用這個來改寫前面的例子。

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(symbol: "F")

if fahrenheitUnit != nil {
    print("succeeded")
    // succeeded
}

let unknownUnit = TemperatureUnit(stmbol: "X")

if unknownUnit == nil {
    print("failed")
    // failed
}

初始化失敗的傳遞

Class, Struct, Enum 的可失敗初始化器可以橫向代理到他們自己他可失敗初始化器。子類的可失敗初始化器也是可以向上代理到父類的可失敗初始化器。

而無論是向上或橫向代理,只要可失敗初始化器有觸發初始化失敗則整個初始化過程就會停止,接下來的初始化代碼都不會被執行。

看官方的例子

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)
    }
}

第一個失敗初始化器先檢查 name 裡面是否為空,如果為空就返回 nil 反之則初始化成功。
第二個繼承了 Product 這個父類並多一個 quantity 的參數, 如果參數小於 1 則返回 nil 反之則初始化成功。
所以接下來可以實例他來確認是否有定義成功。

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// 打印“Unable to initialize zero shirts”

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// 打印“Unable to initialize one unnamed product”

在 twoSocks 裡面既有 sock 也有 quantity = 2 所以都有滿足失敗初始化器的條件所以 print 成功
而 zeroShirts 裡面雖然有 shirt 但在 quantity 這裡小於了 1 所以就直接觸發放失敗初始化器。
而 oneUnnamed 在一開始 name 那裡就直接失敗了,不管 quantity 有沒有成功,後面的 CartItem 自己的失敗初始化器都不會往下執行。

重寫可失敗初始化器

跟其他的初始化器一樣,你可以在子類重寫父類的可失敗初始化器,或者你也可以用子類的非可失敗初始化器重寫一個父類的可失敗初始化器。這樣你可以定義一個不會初始化失敗的子類,即時你父類的初始化器允許失敗。
而當你用子類的非可失敗初始化器重寫父類的可失敗初始化器時,向上代理到父類的可失敗初始化器的唯一方式是直接對父類的可失敗初始化器的返回值進行強制解包。

而可以用非可失敗初始化器重寫可失敗初始化器但所不能反過來處理。

接下來我們看官方的例子
官方定義了 Document 的 Class 。要模擬創建文檔要有 name 屬性不能為空的狀態。

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

下面的實例例子,重寫了父類的可失敗初始化器建立了一個不會初始化失敗的例子。

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
        }
    }
}

這個子類建立了一個不管是 init 或者 init(name:) 都會有一個初始值 [Untitled] 的值。

init! 可失敗初始化器

通常我們在設定可失敗初始化器的時候語法是用添加問號的方式 init? 但你也可以通過在 init 後加驚嘆號來定義 init! ,這個可失敗初始化器會直接觸發一個斷言。

必要初始化器

可以在 class 的初始化器前加上 required 來表明該類的子類都必須實現該初始化器

class someClass {
    required init() {
        
    }
}

在子類重寫父類的必要初始化器時,必須在子類初始化器前也添加 required 。而重寫父類的必要初始化器不需要添加 override 。

class SomeSubclass: someClass {
    required init() {
        
    }
}

OK 今天就到這邊拉。


上一篇
30天的 iOS 修仙道路 (26)
下一篇
30天的 iOS 修仙道路 (28)
系列文
30天的 iOS 修仙道路 站穩腳步基礎篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言