讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 4.3 結構體 >

4.3 結構體

結構體是Swift中非常重要的對象類型。枚舉的case數量固定,實際上它是一種精簡、特殊的對象。類則是另一個極端,它的能力過於強大;它擁有一些結構體缺乏的特性,不過如果不需要這些特性,那麼結構體就是最佳選擇。

在Swift頭文件所聲明的大量對像類型中,只有4個是類(你不大可能用到它們)。與之相反,Swift本身提供的幾乎所有內建對像類型都是結構體。String是個結構體、Int是個結構體、Range是個結構體、Array也是結構體。這表明了結構體的強大功能。

4.3.1 結構體初始化器、屬性與方法

如果一個結構體沒有顯式初始化器,或不需要顯式初始化器(因為結構體沒有存儲屬性,或在聲明中為所有存儲屬性都賦予了默認值),那麼它就會自動擁有一個不帶參數的隱式初始化器init()。比如:


struct Digit {
    var number = 42
}
  

可以通過調用Digit()來初始化上面的結構體。不過,如果添加了自定義的顯式初始化器,那麼隱式初始化器就不復存在了:


struct Digit {
    var number = 42
    init(number:Int) {
        self.number = number
    }
}
  

現在可以調用Digit(number:42),不過不能再調用Digit()了。當然了,你可以添加一個顯式初始化器完成相同的事情:


struct Digit {
    var number = 42
    init {}
    init(number:Int) {
        self.number = number
    }
}
  

現在又可以調用Digit()了,也可以調用Digit(number:42)。

擁有存儲屬性,並且沒有顯式初始化器的結構體會自動獲得來自於實例屬性的隱式初始化器。這叫作成員初始化器。比如:


struct Digit {
    var number : Int // can use \"let\" here
}
  

上述結構體是合法的(事實上,即便使用let而非var來聲明number屬性,它也是合法的),不過似乎我們並沒有實現在聲明中或初始化器中初始化所有存儲屬性的契約。原因在於該結構體自動具有了一個成員初始化器,它會初始化所有屬性。在該示例中,成員初始化器叫作init(number:)。

即便在聲明中為可變存儲屬性賦了默認值,成員初始化器也是存在的;這樣,除了隱式init()初始化器,該結構體還有一個成員初始化器init(number:):


struct Digit {
    var number = 42
}
  

如果添加了自定義的顯式初始化器,那麼成員初始化器就不復存在了(當然,你還是可以提供顯式初始化器完成相同的事情)。

如果結構體擁有顯式初始化器,那麼它必須要實現這個契約:要麼在聲明中,要麼在所有初始化器中完成對所有存儲屬性的初始化。如果結構體有多個顯式初始化器,那麼可以通過調用self.init(...)進行委託。

結構體可以擁有實例屬性與靜態屬性,它們既可以是存儲變量,也可以是計算變量。如果其他代碼想要設置結構體實例的某個屬性,那麼對該實例的引用就必須是個變量(var)而不能是常量(let)。

結構體可以擁有實例方法(包括下標)與靜態方法。如果實例方法設置某個屬性,那麼必須要將其標記為mutating,調用者對該結構體實例的引用必須是個變量(var)而不能是常量(let)。mutating實例方法甚至可以用別的實例替換掉當前實例,只需將self設置為相同結構體的不同實例即可(下標Setter總是mutating的,因此不必顯式標記)。

4.3.2 將結構體作為命名空間

我經常將退化的結構體作為常量的命名空間。之所以稱一個結構體為「退化的」,是因為它只由靜態成員構成;我不會通過該對像類型創建任何實例。不過,這麼使用結構體是完全沒問題的。

比如,假設要在Cocoa的NSUserDefaults中存儲用戶偏好信息。NSUserDefaults是一種字典:每一項都可以通過鍵來訪問,鍵通常是字符串。一個常見的程序錯誤就是在每次需要鍵時都手工寫出這些字符串鍵;如果拼錯了鍵名,那麼在編譯期是不會有任何錯誤出現的,不過代碼將無法正常工作。好的方式是將這些鍵作為常量字符串,並使用字符串的名字;通過這種方式,如果在輸入字符串名的時候出錯了,那麼編譯器會提醒你。擁有靜態成員的結構體非常適合定義這些常量字符串,並且將這些名字形成到一個命名空間中:


struct Default {
    static let Rows = \"CardMatrixRows\"
    static let Columns = \"CardMatrixColumns\"
    static let HazyStripy = \"HazyStripy\"
}
  

上述代碼表示我現在可以通過一個名字來引用NSUserDefaults鍵,如Default.HazyStripy。

如果結構體聲明了靜態成員,其值是相同結構體類型的實例,那麼在需要該結構體類型實例的情況下,提供結構體靜態成員時就可以省略結構體的名字,就好像該結構體是個枚舉一樣:


struct Thing {
    var rawValue : Int = 0
    static var One : Thing = Thing(rawValue:1)
    static var Two : Thing = Thing(rawValue:2)
}
let thing : Thing = .One // no need to say Thing.One here
  

示例本身是我們設想的,不過使用場景卻不是;很多Objective-C枚舉都是通過這種結構體橋接到Swift的(後面還會對此進行介紹)。