Swift初始化的安全方式

edithfang發表於2015-01-09
其實就是安全。在 Objective-C中,init方法是非常不安全的:沒有人能保證init只被呼叫一次,也沒有人保證在初始化方法呼叫以後例項的各個變數都完成初始化,甚至如果在初始化裡使用屬性進行設定的話,還可能會造成各種問題,雖然Apple 也明確說明了不應該在 init 中使用屬性來訪問,但是這並不是編譯器強制的,因此還是會有很多開發者犯這樣的錯誤。

所以 Swift 有了超級嚴格的初始化方法。一方面,Swift 強化了 designated 初始化方法的地位。Swift 中不加修飾的 init方法都需要在方法中保證所有非 Optional 的例項變數被賦值初始化,而在子類中也強制 (顯式或者隱式地) 呼叫super版本的designated 初始化,所以無論如何走何種路徑,被初始化的物件總是可以完成完整的初始化的。

class ClassA {
    let numA: Int
    init(num: Int) {
        numA = num
    }
}

class ClassB: ClassA {
    let numB: Int

    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}


在上面的示例程式碼中,注意在init裡我們可以對let的例項常量進行賦值,這是初始化方法的重要特點。在Swift 中let宣告的值是不變數,無法被寫入賦值,這對於構建執行緒安全的 API 十分有用。而因為 Swift的init只可能被呼叫一次,因此在init中我們可以為不變數進行賦值,而不會引起任何執行緒安全的問題。

與 designated初始化方法對應的是在init前加上convenience關鍵字的初始化方法。這類方法是 Swift初始化方法中的“二等公民”,只作為補充和提供使用上的方便。所有的convenience初始化方法都必須呼叫同一個類中的designated初始化完成設定,另外convenience的初始化方法是不能被子類重寫或者是從子類中以super的方式被呼叫的。

class ClassA {
    let numA: Int
    init(num: Int) {
        numA = num
    }

    convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 10000 : 1)
    }
}

class ClassB: ClassA {
    let numB: Int

    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}


只要在子類中實現重寫了父類convenience方法所需要的init方法的話,我們在子類中就也可以使用父類的convenience初始化方法了。比如在上面的程式碼中,我們在ClassB裡實現了init(num:Int)的重寫。這樣,即使在ClassB中沒有bigNum版本的convenienceinit(bigNum: Bool),我們仍然還是可以用這個方法來完成子類初始化:

let anObj = ClassB(bigNum: true)
// anObj.numA = 10000, anObj.numB = 10001


因此進行一下總結,可以看到初始化方法永遠遵循以下兩個原則:

  • 初始化路徑必須保證物件完全初始化,這可以通過呼叫本型別的 designated 初始化方法來得到保證;
  • 子類的 designated 初始化方法必須呼叫父類的 designated 方法,以保證父類也完成初始化。


對於某些我們希望子類中一定實現的 designated初始化方法,我們可以通過新增required關鍵字進行限制,強制子類對這個方法重寫實現。這樣的一個最大的好處是可以保證依賴於某個designated 初始化方法的convenience一直可以被使用。一個現成的例子就是上面的 init(bigNum:Bool):如果我們希望這個初始化方法對於子類一定可用,那麼應當將init(num:Int)宣告為必須,這樣我們在子類中呼叫init(bigNum:Bool)時就始終能夠找到一條完全初始化的路徑了:

class ClassA {
    let numA: Int
    required init(num: Int) {
        numA = num
    }

    convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 10000 : 1)
    }
}

class ClassB: ClassA {
    let numB: Int

    required init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}


另外需要說明的是,其實不僅僅是對 designated 初始化方法,對於 convenience的初始化方法,我們也可以加上required以確保子類對其進行實現。這在要求子類不直接使用父類中的 convenience初始化方法時會非常有幫助。
來自:碼農網
相關閱讀
評論(1)

相關文章