作者:Thomas Hanning,原文連結,原文日期:2018-03-15 譯者:Sunnyyoung;校對:小鐵匠Linus,numbbbbb;定稿:Forelax
Swift 中有兩種型別的屬性:儲存屬性與計算屬性。儲存屬性將值(常量或者變數)儲存為例項或型別的一部分,而計算屬性沒有儲存值。
提示:這篇文章已經更新至 Swift 4。
儲存屬性
讓我們從儲存屬性開始看起。想象一下你有一個名為 Circle 的類:
class Circle {
var radius: Double = 0
}
let circle = Circle()
circle.radius = 10
print("radius: \(circle.radius)") //radius: 10.0
複製程式碼
Circle 擁有名為 radius
的例項變數,預設值為 0。在 Swift 中,每個例項變數都為一個屬性。因此你可以新增所謂的屬性觀察者。在 Swift 中有兩種型別的屬性觀察者:一種在賦值之前呼叫,另一種在賦值之後呼叫。
在賦值後呼叫的屬性觀察者採用 didSet
關鍵字標記。在我們的示例中,你可以使用它來監測新設定的值:
class Circle {
var radius: Double = 0 {
didSet {
if radius < 0 {
radius = oldValue
}
}
}
}
let circle = Circle()
circle.radius = -10
print("radius: \(circle.radius)") //radius: 0.0
circle.radius = 10
print("radius: \(circle.radius)") //radius: 10.0
複製程式碼
在屬性觀察者中你可以通過變數 oldValue
來訪問屬性的舊值。
你還可以使用 willSet
屬性觀察者,它在賦值之前會被呼叫:
class Circle {
var radius: Double = 0 {
willSet {
print("About to assign the new value \(newValue)")
}
didSet {
if radius < 0 {
radius = oldValue
}
}
}
}
let circle = Circle()
circle.radius = 10 //設定新值 10.0
複製程式碼
在 willSet
中,你可以通過變數 newValue
來訪問屬性的新值。
計算屬性
與儲存屬性不同的是,計算屬性並不會儲存屬性的值。因此在每次呼叫計算屬性時,都要計算該值。在 Circle
類中,你可以將屬性 area
定義為計算屬性:
class Circle {
var radius: Double = 0 {
didSet {
if radius < 0 {
radius = oldValue
}
}
}
var area: Double {
get {
return radius * radius * Double.pi
}
}
}
let circle = Circle()
circle.radius = 5
print("area: \(circle.area)") //area: 78.5398163397448
複製程式碼
計算屬性總是需要一個 getter
。如果缺少 setter
,則該屬性被稱為只讀屬性。下面這個例子很好地說明了 setter
的作用:
import Foundation
class Circle {
var radius: Double = 0 {
didSet {
if radius < 0 {
radius = oldValue
}
}
}
var area: Double {
get {
return radius * radius * Double.pi
}
set(newArea) {
radius = sqrt(newArea / Double.pi)
}
}
}
let circle = Circle()
circle.area = 25
print("radius: \(circle.radius)") //radius: 2.82094791773878
複製程式碼
至此,每次對 area 設定了新的值之後,radius 都會被重新計算。
儲存屬性的初始化
每個儲存屬性在它的物件例項化之後都必須有值。屬性初始化有兩種方法:
- 在
init
方法中初始化值 - 給屬性設定預設的值
下面的例子同時使用了這兩種方法:
class Circle {
var radius: Double
var identifier: Int = 0
init(radius: Double) {
self.radius = radius
}
}
var circle = Circle(radius: 5)
複製程式碼
如果儲存屬性在物件例項化之後沒有值,程式碼無法通過編譯。
懶載入屬性
如果具有預設值的儲存屬性使用了關鍵字 lazy
標記,則其預設值不會立即初始化,而是在第一次訪問該屬性時初始化。
因此,如果該屬性從未被訪問,它將永遠不會被初始化。你可以將這種特性應用於一些特別耗費 CPU 或記憶體的初始化上。
class TestClass {
lazy var testString: String = "TestString"
}
let testClass = TestClass()
print(testClass.testString) //TestString
複製程式碼
該屬性在被訪問之前不會進行初始化。在這個例子中並不容易看出來。但由於初始化也可以在 block 裡面實現,我們可以使它更明顯一些:
class TestClass {
lazy var testString: String = {
print("about to initialize the property")
return "TestString"
}()
}
let testClass = TestClass()
print("before first call")
print(testClass.testString)
print(testClass.testString)
複製程式碼
這個例子的輸出:
before first call
about to initialize the property
TestString
TestString
複製程式碼
這意味著該 block 僅被呼叫一次 - 第一次訪問該屬性的時候。由於儲存屬性是可變的,因此可以更改初始值。
型別屬性
型別屬性是類的一部分,但不是例項的一部分,型別屬性也被稱為靜態屬性。儲存屬性和計算屬性都可以是型別屬性。型別屬性的關鍵字是 static
:
class TestClass {
static var testString: String = "TestString"
}
print("\(TestClass.testString)") //TestString
複製程式碼
如你所見,它們使用類名而不是例項物件來訪問它們。此外,由於型別屬性沒有初始化方法,它總是需要一個預設值。
擁有私有 Setter 的公共屬性
正如我在 另一篇文章 中介紹的那樣,這是一種常見的情況,你不想提供一個公共的 setter,而是提供一個私有的 setter。這是封裝的基本原則。這樣只有類本身可以操作該屬性,但仍可從類外部訪問讀取它。
來看下面的例子:
public class Circle {
public private(set) var area: Double = 0
public private(set) var diameter: Double = 0
public var radius: Double {
didSet {
calculateFigures()
}
}
public init(radius:Double) {
self.radius = radius
calculateFigures()
}
private func calculateFigures() {
area = Double.pi * radius * radius
diameter = 2 * Double.pi * radius
}
}
let circle = Circle(radius: 5)
print("area: \(circle.area)") //area: 78.5398163397448
print("diameter: \(circle.diameter)") //diameter: 31.4159265358979
circle.area = 10 //編譯錯誤:無法對 'area' 屬性進行賦值,因為 setter 方法不可訪問
複製程式碼
這裡的屬性 area
和 diameter
可以從類的外部訪問,但只能在類內部賦值。為此你必須使用 public private(set)
的組合。根據本人的經驗,這個特性在 iOS 開發中很少使用,但它對寫出更少 bug 的程式碼很有幫助。
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 swift.gg。