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