讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 3.6 延遲初始化 >

3.6 延遲初始化

術語延遲並非貶義;它是對一種重要行為的正式描述。如果存儲變量在聲明時被賦予一個初始值,並且使用了延遲初始化,那麼直到運行著的代碼訪問了該變量的值時才會計算初始值並完成賦值。

在Swift中,有3種類型的變量可以做到延遲初始化:

全局變量

全局變量自動就是延遲初始化的,如果你問自己,它們何時應該初始化,那麼這就是答案。當應用啟動時,文件與頂層代碼都會執行,這時初始化全局變量是沒有意義的,因為應用甚至還沒有運行。這樣,全局初始化必須要延遲到後面某個有意義的時間點處。因此,全局變量初始化直到其他代碼首次引用它們時才會發生。在底層,該行為是由dispatch_once保護的;這使得初始化只會執行一次並且是線程安全的。

靜態屬性

靜態屬性的行為非常類似於全局變量,並且也是出於相同的原因。(Swift中並沒有存儲類屬性,因此類屬性是無法初始化的,也不能做到延遲初始化。)

實例屬性

在默認情況下,實例屬性不是延遲初始化的,不過可以在聲明中通過關鍵字lazy讓它變成延遲初始化。該屬性必須要通過var聲明,而不是let。如果在代碼獲取屬性值之前有其他代碼對該屬性賦值,那麼屬性的初始化器就永遠都不會執行。

延遲初始化器通常用於實現單例。單例是一種設計模式,所有代碼都可以訪問某個類的一個單獨的共享實例:


class MyClass {
    static let sharedMyClassSingleton = MyClass
}  

其他代碼可以通過MyClass.sharedMyClassSingleton獲取到對MyClass單例的引用。直到其他代碼首次這麼調用時,單例實例才會創建出來;隨後,無論調用多少次,返回的總是這個相同的實例。(如果這是計算只讀屬性,其getter調用了MyClass()並返回該實例,那麼情況就不是這樣的了,知道原因嗎?)

現在來談談實例屬性的延遲初始化。為何需要這個特性呢?一個原因是顯而易見的:初始值的生成代價可能會很高,因此你希望只在需要時才生成。不過還有另外一個不那麼明顯的原因,而且這個原因更為重要:延遲初始化器可以做到正常的初始化器做不到的事情。特別地,它可以引用到實例,正常的初始化器卻做不到這一點,因為在正常的初始化器運行時,實例還不存在(我們還在創建實例的過程中,因此實例尚未準備好)。與之相反,延遲初始化器直到實例已經創建出來後的某個時間點才會運行,因此可以引用到實例。比如,如果沒有將arrow屬性聲明為lazy,那麼如下代碼就是不合法的:


class MyView : UIView {
    lazy var arrow : UIImage = self.arrowImage
    func arrowImage  -> UIImage {
        // ... big image-generating code goes here ...
    }
}  

常見的寫法是通過一個定義與調用匿名函數來初始化延遲實例屬性:


lazy var prog : UIProgressView = {
    let p = UIProgressView(progressViewStyle: .Default)
    p.alpha = 0.7
    p.trackTintColor = UIColor.clearColor
    p.progressTintColor = UIColor.blackColor
    p.frame = CGRectMake(0, 0, self.view.bounds.size.width, 20)
    p.progress = 1.0
    return p
}  

語言中有一些小陷阱:延遲實例屬性不能擁有Setter觀察者,並且也沒有lazy let(因此無法將延遲實例屬性設為只讀)。不過,這些限制並不嚴重,因為對於存儲屬性來說,計算屬性做不到的事情也不要指望lazy屬性能夠做到,如示例3-1所示。

示例3-1:手工實現延遲屬性


private var lazyOncer : dispatch_once_t = 0
private var lazyBacker : Int = 0
var lazyFront : Int {
    get {
        dispatch_once(&self.lazyOncer) {
            self.lazyBacker = 42 // expensive initial value
        }
        return self.lazyBacker
    }
    set {
        dispatch_once(&self.lazyOncer) {}
        // will set
        self.lazyBacker = newValue
        // did set
    }
}  

在示例3-1中,原則在於只有lazyFront可以被外界訪問;lazyBacker是其底層存儲,lazyOncer使得一切只出現正確的次數。lazyFront現在是個普通的計算變量,因此我們可以在設置時觀察它(在其Setter函數中加入額外代碼,位於「will set」與「did setter」註釋處),也可以將其設為只讀(將整個Setter刪除)。