Swift 中變量的使用細節

變量(variable)是任何一門語言的構成基礎, 對 Swift 也不例外. 大家可能會想, 這東西還有什么"細節"可言, 正常使用不就行了.
其實, 在 Swift 中, 變量使用起來還是有一些細節問題需要注意以及提防的, 作者就根據自己的實際經驗總結了如下一些東西, 僅供大家參考.
由于這里探討的是使用細節問題, 因此基本的變量聲明、操作以及相關概念等此文便不再贅述.
本文將分成如下 3 部分進行說明.

1. stored variable 和 computed variable

2. Observer(觀察器)

3. 懶加載(重點說明!!!)


1. stored variable 和 computed variable

Swift 中的變量分為 stored variable 和 computed variable.
stored variable 顧名思義, 就是我們一般意義上理解的可以進行賦值和取值的變量.

var number: Int = 10
上面的 number 就是一個 stored variable, 對其進行正常的讀(取值)寫(賦值)操作即可, 因為 stored variable 是可以存儲值的.

computed variable, 字面意思為"計算型的變量", 雖然也叫"變量", 但要注意, 這個家伙是根本沒法存儲值的.
來看下面的例子, 這里定義了一個名為 number 的 computed variable:

var number: Int {
    get {
        return 10
    }
    set {
        print("number 被設置為了 \(newValue)")
    }
}

下面對 number 進行我們"想當然"的讀寫操作:

number = 100        // 輸出結果為 " number 被設置為了 100 "
print(number)      // 輸出結果為 " 10 "

What? 明明給 number 賦了新值 100, 并且輸出結果也明確指出 number 被設置為了 100, 為什么再次打印 number 的值時, 卻還是 10 ???

切記, computed variable 根本不會存儲我們主觀上認為的"值"!!! 它實質上是一些待執行的代碼!!!
number 被賦值時, set 中的代碼被執行. 同理, 當執行讀取 number 的操作時, get 中的代碼被執行.

再次重申, computed variable 只是一些待執行的代碼!!!

在對 computer variable 賦值時, 我們能夠取到賦給其的新值, 那就是 set 中的 newValue, 至于這個值要怎么用, 那就看實際的需要了. 但要注意, newValue 不會存儲至 computed variable中! 畢竟, computed variable 只是一些待執行的代碼! 重要的事情說三遍!!!

可能有人會想, 畢竟 computed variable 叫做"變量", 如果不能像 stored variable 那樣進行很直觀的讀寫操作, 總覺得很奇怪, 我下面給出了一種用 computed variable 實現 stored variable 效果的思路.

這里首先定義一個 PositiveNumber 類, 用來表示正數:

class PositiveNumber {
    private var _value: Double = 0.0
    var value: Double {
        get {
            return _value
        }
        set {
            _value = newValue
        }
    }
}

我為 PositiveNumber 類定義了兩個屬性, 分別為私有的store property _value 和可供外部訪問的接口: 一個 computed property value.
是不是很面熟? 沒錯, 這跟 OC 中定義一個 @property 是完全相同的!

let number = PositiveNumber()
number.value = 10       // 利用 computed property 的 set 方法為 _value 賦值
print(number.value)   // 利用 computed property 的 get 方法讀取 _value 的值

看到這里, 你可能會想, 如此大費周折, 只做了一件 stored property 很容易就能做到的事兒, 這不是閑的嗎?

我們仔細思考一下上例, number 是一個 PositiveNumber 的實例, 在上述實現方式下, 我們可以為 number.value 賦任意值, 如: number.value = -10
這在邏輯上明顯是不合適的, 因為 number 是一個"正數", 所以我們要對賦給 number.value 的值進行一定的處理. 這種邏輯交給 computed property 就再合適不過了.
修改上述代碼如下:

class PositiveNumber {
    private var _value: Double = 0.0
    var value: Double {
        get {
            return _value
        }
        set {
            if (newValue <= 0) {
                print("賦值不合理!")
            } else {
                _value = newValue
            }
        }
    }
}

這樣, 若再執行 number.value = -10 則會執行判斷的邏輯, 從而使對 _value 的賦值操作更加嚴謹.

當然, computed property 的作用不僅如此, 下面說到懶加載時還會提到.


2. Observer(觀察器)

Observer(即 willSet 和 didSet 方法) 是用在 stored variable 上的, 畢竟只有能存儲值的東西才有被觀察的價值!
基本的使用方法這里就不贅述了, 只對使用時的一些細節做一下說明.

  • 當 stored variable 被初始化時, 觀察器方法不會被調用
  • 如果在 didSet 方法中改變了 stored variable 本身的值, 觀察器方法也不會被調用. 這個邏輯很容易理通, 因為若在觀察器方法中修改 stored variable 的值還會調用觀察器方法的話, 那么便會導致觀察器方法的無限循環調用!
  • 在 willSet 方法中去操作 stored variable, 其實是對舊值進行操作, 新值為 newValue
  • 在 didSet 方法中去操作 stored variable, 其實是對新值進行操作, 舊值為 oldValue

參考下面的代碼

class PositiveNumber {
    var value: Double {
        willSet {
            print("willSet 方法被調用")
            print("在 willSet 中, value = \(value), newValue = \(newValue)")
        }
        didSet {
            print("didSet 方法被調用")
            print("在 didSet 中, value = \(value), oldValue = \(oldValue)")
        }
    }
    
    init(value: Double) {
        self.value = value
    }
}

此處測試一下
let number = PositiveNumber(value: 10.0)
此時, 控制臺未輸出任何信息! 可見這個初始化的操作并沒有調用觀察器方法.

再來執行 number.value = 20

控制臺輸出信息
willSet 方法被調用
在 willSet 中, value = 10.0, newValue = 20.0
didSet 方法被調用
在 didSet 中, value = 20.0, oldValue = 10.0

由上例可以看到觀察器調用時的一些細節, 大家在使用時注意一下就好.


3. 懶加載

對于開發者來說,懶加載最被人熟知的優點就在于只在需要某個 variable 時, 才去進行加載.其實, 懶加載還能處理一些普通的初始化方法處理不了的情況.
以初始化一個類的實例為例, 在普通的初始化方法中, 若該實例的初始化過程還未結束, 那么開發者是無法在該初始化方法中引用該實例的屬性和方法的. 而懶加載則可以解決這個問題, 因為當需要懶加載某個屬性時, 該實例變量已經初始化完畢, 因此開發者可以在懶加載的流程中去任意使用該實例的屬性以及方法!

下面說明一下懶加載的使用細節.

  • 全局變量默認都是懶加載的! 其實仔細想想, 蘋果的這種設計還是非常合理的. 對于一個全局變量來說, 何時加載它最合適呢? 顯然程序一啟動就加載的方式不太合理, 于是乎, 還是在需要它時再加載吧!
  • static 的屬性默認都是懶加載的! static 的屬性某種程度上同全局變量很類似, 例如一個 struct 中含有 static 的屬性, 那么這個屬性是屬于這個 struct 類型的, 而不是屬于某個具體的 struct 變量! 于是問題又來了, 何時加載這個屬性最合適? 依然還是需要它時再加載吧!
  • 實例屬性默認都不是懶加載的! 注意! 都不是懶加載的! 若要令其變為懶加載, 那就在聲明時加 lazy, 并且這個實例屬性必須是 var, 而不能是 let. 同時, 懶加載的實例屬性也無法實現觀察器方法, 即沒有 willSet 和 didSet 方法.

雖然 Swift 已經提供給了我們一種超級給力的實現懶加載的方式, 即只要一句 lazy 就搞定了, 但上述最后一條細節還是留給了我們很多遺憾!

考慮這樣一種需求: 某個實例屬性一定要采用懶加載的方式, 而且要對其實現觀察器的功能, 即可觀測其值的變化并進行一定的邏輯處理.
再考慮另一種需求: 某個實例屬性一定要采用懶加載的方式, 并且加載完成后是只讀的, 不能對其進行修改.

乍一看, 這些需求明顯是在給 Swift 原生的懶加載方式找茬嘛! 上述需求, 原生的 lazy var 一條都實現不了!!! 首先, 懶加載的實例屬性本身就無法實現觀察器方法, 同時 lazy var 這種形式的聲明又導致該實例變量可以被修改, 此時會想, 如果有個 lazy let 就好了...

但是, 上述需求并不過分, 開發中也會碰到, 怎么辦? 只能手寫一個滿足上述需求的懶加載了. 下面只提供了一種思路, 僅供大家參考

class PositiveNumber {
    private var token: dispatch_once_t = 0
    private var _value: Double = 0.0
    var value: Double {
        get {
            dispatch_once(&token) {
                // 一些非常耗時的初始化 _value 的流程
            }
            return _value
        }
        set {
            _value = value
        }
    }
}

其實仔細想想, 懶加載在某種程度上同 computed variable 是相同的, 即只有需要時才調用! 那么就可以利用 computed variable 的這些特點來人造一個懶加載方式.

上段代碼采用了本文中第 1 點提到的方式, 實現了通過一個 computed variable 來操作一個 stored variable 的功能, 然后利用 GCD 的一次性代碼實現了懶加載的"只加載一次"操作. 至此, 我們手寫了一個與使用 lazy 來實現懶加載操作相同的方法.

但是, 要注意! 上段代碼的靈活性更強一些, 包括:

  • 若想實現觀察器方法, 只要在 set 方法中構建相應的邏輯即可. 例如, 在 _value = value 前添加 willSet 中希望具有的邏輯, 在 _value = value 后添加 didSet 中希望具有的邏輯即可
  • 若想實現 lazy let 形式的懶加載, 只需要把 set 方法去掉即可, 那么屬性初始化后就變成只讀的了, 外界無法對其進行任何修改
  • 采用了 GCD 的一次性代碼, 可以保證線程安全. 根據 Apple 官方的《The Swift Programming Language》, 普通的采用 lazy var 形式的懶加載, 無法保證線程安全. 一旦某個線程對某一實例屬性的懶加載過程未結束, 而另一個線程同時又操作了該實例屬性, 那么會導致又一次加載該屬性, 此時該屬性便被初始化了多次, 已不再具有"懶"的特點了
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,932評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 天花板上的想象, 存在真理與逃亡。 為何海涅和阿格瑞斯刀劍相向, 老鼠卻提著花籃? 噠噠的馬蹄, 是結束的序曲, ...
    晚秋暮月閱讀 294評論 0 3
  • 發自iPhone客戶端。 用于測試 Markdown支持。
    ted005閱讀 582評論 0 51
  • 家庭理財是對家庭收入和支出進行分析、預測并加以合理安排的一種財務規劃活動。那么,搞好家庭理財到底可以帶來哪些好處呢...
    財管評測師閱讀 570評論 0 0