站在OC的基礎(chǔ)上快速理解Swift的類與結(jié)構(gòu)體
首先我發(fā)現(xiàn)在編寫Swift代碼的時候,經(jīng)常會遇到Xcode不能提示,卡頓,直接閃退等問題,尤其是在Swift和OC混編時。(不知道其他開發(fā)者是否也有這樣的經(jīng)歷,但是我相信這樣的問題,很快會得到解決
然后感覺Swift并不像網(wǎng)上很多朋友說的那樣簡單。有很多細(xì)節(jié)問題是值得注意的,甚至有很多思路是顛覆了傳統(tǒng)的開發(fā)語言的!又有很多特性是結(jié)合了其他編程語言的優(yōu)點(diǎn)!
Swift,我個人覺得是趨勢,是目前最前衛(wèi)的開發(fā)語言,結(jié)合了眾多開發(fā)語言的優(yōu)點(diǎn)!
網(wǎng)上已經(jīng)有很多Swift相關(guān)的論文和博客了,這里我不做推薦和轉(zhuǎn)載了!我歸納一下類和結(jié)構(gòu)體,以及相關(guān)的知識吧!
Swift中,類和結(jié)構(gòu)體都是對數(shù)據(jù)和方法進(jìn)行封裝的常用做法!首先我們來看看他們的共同之處:
都可以有屬性和方法;
都有構(gòu)造器;
都支持附屬腳本;
都支持?jǐn)U展;
都支持協(xié)議。
然后我們來看看他們的不同之處:
類有繼承;
結(jié)構(gòu)體有一個自動生成的逐一初始化構(gòu)造器;
在做賦值操作時,結(jié)構(gòu)體總是被拷貝(Array有特殊處理);
結(jié)構(gòu)體可以聲明靜態(tài)的屬性和方法;
從設(shè)計(jì)模式的角度來分析,類的設(shè)計(jì)更側(cè)重于對功能的封裝,而結(jié)構(gòu)體的設(shè)計(jì)更側(cè)重于對數(shù)據(jù)的封裝。(汽車與書架來區(qū)分類與結(jié)構(gòu)體)
一、構(gòu)造過程
1. 默認(rèn)值
在OC 類和結(jié)構(gòu)的成員屬性會隱式的給出默認(rèn)值,對象的默認(rèn)值是nil,基本數(shù)據(jù)類型的默認(rèn)值是0。但是在 Swift 中屬性必須顯示的設(shè)置默認(rèn)值,Swift會在調(diào)用構(gòu)造器之前調(diào)用默認(rèn)值構(gòu)造器對所有在設(shè)置了默認(rèn)值的屬性進(jìn)行初始化。
當(dāng)一個構(gòu)造過程完成的時候,被構(gòu)造的類或者結(jié)構(gòu)體的實(shí)例的存儲屬性都會是有值的,否則編譯錯誤!
- class A : SuperClass {
- var _a = 1 // 默認(rèn)值 = Int(1)
- var _b: Int? // 默認(rèn)值 = nil
- var _c: Int // 無默認(rèn)值,必須在構(gòu)造器中賦值
- var _d: Int! // 默認(rèn)值 = nil
- init() {
- _c = 1 // 在調(diào)用父類的構(gòu)造器之前,必須給_c賦值,否則編譯錯誤
- super.init()
- }
- ...
- }
2. 構(gòu)造器
類似與OC的 -(instance)init 方法。和OC***的區(qū)別是 OC 初始化完成后返回的是這個對象的指針,而Swift初始化完成后返回的是這個對象的引用。
根據(jù)構(gòu)造器的實(shí)際構(gòu)造過程可將構(gòu)造器分為 便利構(gòu)造器 和 指定構(gòu)造器,但只有指定構(gòu)造器才是真實(shí)的構(gòu)造器,便利構(gòu)造器只是為指定構(gòu)造器添加一個便利的參數(shù)傳入,或者增加一些附加操作。
以下幾點(diǎn)在開發(fā)的時候需要注意:
類和結(jié)構(gòu)體都需要在構(gòu)造器完成對所有存儲屬性的初始化;
子類在調(diào)用父類的構(gòu)造器之前必須確保本類聲明的所有的存儲屬性都已經(jīng)有值了;
便利構(gòu)造器必須直接或者間接的調(diào)用本類的指定構(gòu)造器。
- class A : SomeClass {
- var _a: Int
- var _b = 1_000
- // 指定構(gòu)造器
- init() {
- self._a = 5 // 如果是這樣聲明的 'var _a: Int = 5' or 'var _a = 5',那么init()構(gòu)造器可以不寫而直接調(diào)用
- super.init()
- }
- // 指定構(gòu)造器
- init(a: Int) {
- self._a = a // 因?yàn)?nbsp;_a 屬性沒有默認(rèn)值,所以在調(diào)用 super.init() 前必須給 _a 賦值,否則編譯錯誤!
- super.init()
- }
- // 便利構(gòu)造器
- convince init(a: Int, b: Int) {
- self.init(a: a + b) // 直接調(diào)用指定構(gòu)造器
- }
- // 便利構(gòu)造器
- convince init(a: Int, b: Int, c: Int) {
- self.init(a: a, b: b + c) // 間接調(diào)用指定構(gòu)造器
- }
- ...
- }
3. 析構(gòu)器
和OC的 dealloc 很像,這里不多做解釋了。
- class A {
- ...
- deinit {
- //... 析構(gòu)器內(nèi)部處理
- }
- ...
- }
二、屬性
OC中的類有屬性,也有實(shí)例變量。但Swift中將類的實(shí)例變量和屬性統(tǒng)一用屬性來實(shí)現(xiàn)。
1. 存儲屬性
簡單來說就是一個成員變量/常量。Swift可以為存儲屬性增加屬性監(jiān)視器來響應(yīng)屬性值被設(shè)置時的變化。
- class SomeClass {
- let _a = 100 // 常量
- var _b: Int // 沒有默認(rèn)值的變量
- var _c = 200 // 默認(rèn)值=200的Int變量
- var _d: Int = 200 { // 默認(rèn)值=200的Int變量,如果增加了屬性監(jiān)視器,必須顯示的聲明變量類型
- didSet {
- println("A 的 _c 屬性 didSet = old:\(oldValue) -> \(self._c)") // oldValue 表示曾經(jīng)的值
- }
- willSet {
- println("A 的 _c 屬性 willSet = \(self._c) -> new:\(newValue)") // newValue 表示將會被設(shè)置的值
- }
- }
- }
2. 計(jì)算屬性
計(jì)算屬性并不存儲任何數(shù)據(jù),他只是提供 set/get 的便利接口。
- class A {
- class var a: Int { // 這是類的計(jì)算屬性 (區(qū)別于下面類的實(shí)例的計(jì)算屬性)
- return 1001
- }
- private(set) var _b = 100 // 外部可以訪問,但不能修改
- private var _a = 100 // 私有變量,外部不能訪問
- var a: Int { // 只讀計(jì)算屬性
- return _a
- }
- var b: Int { // 可讀可寫的計(jì)算屬性
- get {
- retrun _a
- }
- set {
- _a = newValue // newValue 表示的是輸入的值
- }
- }
- }
3. 延遲存儲屬性
相信大家都聽說過延遲加載(懶加載),就是為了避免一些無謂的性能開銷,在真正需要該數(shù)據(jù)的時候,才真正執(zhí)行數(shù)據(jù)加載操作。 Swift可以使用關(guān)鍵字 lazy 來聲明延遲存儲屬性,延遲存儲屬性在默認(rèn)值構(gòu)造器中不會被初始化,而是在***次使用前進(jìn)行初始化! 雖然沒被初始化,但是編譯器會認(rèn)為他是有值的。
全局的常量或者變量都是延遲加載的, 包括結(jié)構(gòu)體的靜態(tài)屬性也是延遲加載的。
- let some = A()
- class A {
- lazy var view = UIView(frame: CGRectZero) // 定義了一個延遲存儲屬性 ...
- }
4. 靜態(tài)屬性
結(jié)構(gòu)體可以使用關(guān)鍵字 static 來聲明靜態(tài)存儲屬性。(枚舉也可以有) Swift的類不支持靜態(tài)屬性,也不支持靜態(tài)臨時變量。 這可以作為Swift中聲明單例的一種實(shí)現(xiàn)方:
- class Tools {
- class func sharedInstance() -> Tools {
- struct Static {
- static let sharedInstance = QNTools()
- }
- return Static.sharedInstance
- }
- }
三、方法
Swift的類和結(jié)構(gòu)體都可以定義自己的方法!(OC的結(jié)構(gòu)體是沒有方法的)
1. 參數(shù)
一個方法的參數(shù)有局部參數(shù)名稱和外部參數(shù)名稱,一樣時寫一個即可! Swift的方法的參數(shù)非常靈活,可以是值,可以是引用,可以是閉包,可以是元組... Swift的方法的返回值跟參數(shù)一樣靈活
這里給出一個簡單的加法方法來展示方法的參數(shù):
- class A {
- // eg. 一個簡單的相加方法
- // 完整版
- class func sum1(let a/*外部參數(shù)名稱*/ aValue/*內(nèi)部參數(shù)名稱*/: Int, let b/*外部參數(shù)名稱*/ bValue/*內(nèi)部參數(shù)名稱*/: Int) -> Int {
- return aValue + bValue
- }
- // 當(dāng)函數(shù)參數(shù)的外部和內(nèi)部參數(shù)相同的時候,可以只寫一個, 此時***個參數(shù)的名稱在調(diào)用時是隱藏的
- class func sum2(a: Int, b: Int) -> Int {
- return a + b
- }
- // 使用 _ 符號,可以在調(diào)用的時候隱藏函數(shù)參數(shù)名稱,***個參數(shù)默認(rèn)就是隱藏的
- class func sum3(a: Int, _ b: Int) -> Int {
- // 內(nèi)嵌函數(shù)的參數(shù)名稱,都是可以隱藏的。而不用使用 _ 符號聲明
- func sum4(a: Int, b: Int) -> Int {
- return a + b
- }
- return sum4(a, b)
- }
- // 可使用 let/var 關(guān)鍵字來聲明參數(shù)是作為常量還是變量被傳入,(如果是常量,可以不用顯示的寫 let)
- class func sum4(let a: Int, _ b: Int) -> Int {
- // 內(nèi)嵌函數(shù)的參數(shù)名稱,都是可以隱藏的。而不用使用 _ 符號聲明
- func sum4(a: Int, b: Int) -> Int {
- return a + b
- }
- return sum4(a, b)
- }
- // 可使用 let/var 關(guān)鍵字來聲明參數(shù)是作為常量還是變量被傳入,(如果是常量,可以不用顯示的寫 let)
- class func sum5(let a: Int, let _ b: Int) -> Int {
- // 內(nèi)嵌函數(shù)的參數(shù)名稱,都是可以隱藏的。而不用使用 _ 符號聲明
- return a + b
- }
- class func sum6(var a: Int, var _ b: Int) -> Int {
- // 內(nèi)嵌函數(shù)的參數(shù)名稱,都是可以隱藏的。而不用使用 _ 符號聲明
- a++
- b++
- return a + b
- }
- // 可使用 inout 關(guān)鍵字,傳引用
- class func add(inout value: Int) {
- value++
- }
- }
- A.sum1(a: 1, b: 2) // result: 3
- A.sum2(1, b: 2) // result: 3
- A.sum3(1, 2) // result: 3
- var aTemp: Int = 1001 // aTemp = 1001
- A.add(&aTemp)
- aTemp // aTemp = 1002
- A.sum5(1, 2) // result: 3
- A.sum6(1, 2) // result: 5
2. 實(shí)例方法
類或者結(jié)構(gòu)體的實(shí)例所擁有的方法!
- class A {
- private(set) var a: Int = 100
- func reset() -> Void {
- self.a = 100
- }
- }
- struct S {
- var a: Int = 100
- mutating func reset() -> Void { // 注意: 在結(jié)構(gòu)體和枚舉的實(shí)例方法中如果需要修改屬性,則需要增加 mutating 字段
- self.a = 100
- }
- }
3. 類方法
Swift 中類的本身也是一個實(shí)例。他沒有存儲屬性、但擁有計(jì)算屬性和方法!
參考 “1.參數(shù)” 中的示例
4. 靜態(tài)方法
結(jié)構(gòu)體可以使用關(guān)鍵字 static 來聲明靜態(tài)方法。
- struct S {
- static func name() -> String {
- return "Liuyu"
- }
- }
四、附屬腳本
Swift 提供了附屬腳本的語法來簡化類似查詢的行為。如訪問一個數(shù)組的第n個元素,array[n], 訪問一個字典的值 dictionary[“key”],這些都可以通過附屬腳本來實(shí)現(xiàn)。 這里給出一個通過字符串類型的索引來查詢數(shù)組中的元素的例子。
- // 擴(kuò)展一個通過字符串類型的位置來訪問數(shù)據(jù)的附屬腳本
- extension Array {
- subscript(index: String) -> T? {
- if let iIndex = index.toInt() {
- return self[iIndex]
- }
- return nil
- }
- }
- let array = ["Liu0", "Liu1", "Liu2"]
- array[1] // result : Liu1
- array["1"]! // result : Liu1
五、繼承
和OC一樣,Swift類可以通過繼承來獲取父類的屬性和方法(有限制)。 Swift的類的在繼承上增加了很多安全措施
- class SomeSuperClass {
- func someFunc1() {
- ...
- }
- // 定義不能被子類重寫的方法,需要加上 final 關(guān)鍵字
- final func someFunc2() {
- ...
- }
- }
- class SomeClass : SomeSuperClass {
- // 重載父類的方法,需要加上 override 關(guān)鍵字
- override func someFunc1() {
- ...
- super.someFunc1()
- }
- // 不能被子類重寫的方法
- override func someFunc2() { // Error
- ...
- super.someFunc2()
- }
- }
六、擴(kuò)展
擴(kuò)展就是向一個已有的類、結(jié)構(gòu)體或枚舉類型添加新功能。 這包括在沒有權(quán)限獲取原始源代碼的情況下擴(kuò)展類型的能力(即逆向建模)。 擴(kuò)展和OC中的類別(categories)類似,但又有很多細(xì)微的區(qū)別:
擴(kuò)展沒有名字,一旦擴(kuò)展就會應(yīng)用到整個工程(模塊)
在擴(kuò)展中如果要重載現(xiàn)有的方法,需加上override關(guān)鍵字 (不建議修改現(xiàn)有的方法)
可定義新的嵌套類型
這里給出一個數(shù)學(xué)項(xiàng)目中計(jì)算距離時的一個擴(kuò)展
- typealias Distance = Double
- extension Distance {
- var km: Double { return self/1_000.0 }
- var m : Double { return self }
- var cm: Double { return self*100.0 }
- var mm: Double { return self*1_000.0 }
- }
- let distance = Distance(1000) // 1000m的距離
- distance.km // result : 1.0 (km)
- distance.m // result : 1000.0 (m)
- distance.cm // result : 100.0 (cm)