swift學習筆記3——類、結構體、枚舉

以前學習swift時的我的筆記,根據github:the-swift-programming-language-in-chinese學習、總結,將重要的內容提取,加以理解後整理爲學習筆記,方便之後查詢用。詳細能夠參考the-swift-programming-language-in-chinese,或者蘋果官方英文版文檔html

當前版本是swift2.2ios

類和結構體

與 Objective-C 語言不一樣的是,Swift 容許直接設置結構體屬性的子屬性。git

實際上,在 Swift 中,全部的基本類型:整數(Integer)、浮點數(floating-point)、布爾值(Boolean)、字符串(string)、數組(array)和字典(dictionary),都是值類型,而且在底層都是以結構體的形式所實現。在 Swift 中,全部的結構體和枚舉類型都是值類型。這意味着它們的實例,以及實例中所包含的任何值類型屬性,在代碼中傳遞的時候都會被複制。github

「等價於」(用三個等號表示,===)與「等於」(用兩個等號表示,==)的不一樣:swift

「等價於」表示兩個類類型(class type)的常量或者變量引用同一個類實例。
「等於」表示兩個實例的值「相等」或「相同」,斷定時要遵守設計者定義的評判標準,所以相對於「相等」來講,這是一種更加合適的叫法。數組

Swift 中,許多基本類型,諸如String,Array和Dictionary類型均以結構體的形式實現。這意味着被賦值給新的常量或變量,或者被傳入函數或方法中時,它們的值會被拷貝。安全

Objective-C 中NSString,NSArray和NSDictionary類型均以類的形式實現,而並不是結構體。它們在被賦值或者被傳入函數或方法時,不會發生值拷貝,而是傳遞現有實例的引用。app

以上是對字符串、數組、字典的「拷貝」行爲的描述。在你的代碼中,拷貝行爲看起來彷佛總會發生。然而,Swift 在幕後只在絕對必要時才執行實際的拷貝。Swift 管理全部的值拷貝以確保性能最優化,因此你不必去迴避賦值來保證性能最優化。ide

常量結構體的存儲屬性函數

若是建立了一個結構體的實例並將其賦值給一個常量,則沒法修改該實例的任何屬性,即便有屬性被聲明爲變量也不行:

注意
必須將延遲存儲屬性聲明成變量(使用 var 關鍵字),由於屬性的初始值可能在實例構造完成以後纔會獲得。而常量屬性在構造過程完成以前必需要有初始值,所以沒法聲明成延遲屬性

這種行爲是因爲結構體(struct)屬於值類型。當值類型的實例被聲明爲常量的時候,它的全部屬性也就成了常量。
屬於引用類型的類(class)則不同。把一個引用類型的實例賦給一個常量後,仍然能夠修改該實例的變量屬性。

若是一個被標記爲 lazy 的屬性在沒有初始化時就同時被多個線程訪問,則沒法保證該屬性只會被初始化一次。

存儲屬性與計算屬性

存儲屬性沒有get和setter方法,延遲存儲屬性即lazy懶加載

計算屬性,有get方法和可選的set方法(必須有get方法),在set方法裏面給計算屬性賦值會形成循環調用

屬性觀察器

屬性觀察器監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,即便新值和當前值相同的時候也不例外。

能夠爲除了延遲存儲屬性以外的其餘存儲屬性添加屬性觀察器,也能夠經過重寫屬性的方式爲繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器

父類的屬性在子類的構造器中被賦值時,它在父類中的 willSet 和 didSet 觀察器會被調用,隨後纔會調用子類的觀察器

在第一個檢查過程當中,willSet,didSet 屬性觀察器再次將其設置成了不一樣的值,但這不會形成屬性觀察器被再次調用。有了屬性觀察器,就不該再有getter和setter方法

類型屬性

跟實例的存儲型屬性不一樣,必須給存儲型類型屬性指定默認值,由於類型自己沒有構造器,也就沒法在初始化過程當中使用構造器給類型屬性賦值。
存儲型類型屬性是延遲初始化的,它們只有在第一次被訪問的時候纔會被初始化。即便它們被多個線程同時訪問,系統也保證只會對其進行一次初始化,而且不須要對其使用 lazy 修飾符。

繼承

在 Swift 中,繼承是區分「類」與其它類型的一個基本特徵。

重寫屬性

你能夠重寫繼承來的實例屬性或類型屬性,提供本身定製的 getter 和 setter,或添加屬性觀察器使重寫的屬性能夠觀察屬性值何時發生改變。

你能夠將一個繼承來的只讀屬性重寫爲一個讀寫屬性,只須要在重寫版本的屬性裏提供 getter 和 setter 便可。可是,你不能夠將一個繼承來的讀寫屬性重寫爲一個只讀屬性。

若是你在重寫屬性中提供了 setter,那麼你也必定要提供 getter。若是你不想在重寫版本中的 getter 裏修改繼承來的屬性值,你能夠直接經過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字。

你不能夠爲繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。這些屬性的值是不能夠被設置的,因此,爲它們提供willSet或didSet實現是不恰當。
此外還要注意,你不能夠同時提供重寫的 setter 和重寫的屬性觀察器。若是你想觀察屬性值的變化,而且你已經爲那個屬性提供了定製的 setter,那麼你在 setter 中就能夠觀察到任何值變化了。

存儲屬性的初始賦值

類和結構體在建立實例時,必須爲全部存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。可選類型的屬性能夠不設,它將自動初始化爲nil

你能夠在構造器中爲存儲型屬性賦初值,也能夠在定義屬性時爲其設置默認值。如下小節將詳細介紹這兩種方法。

當你爲存儲型屬性設置默認值或者在構造器中爲其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀察者

若是你在定義構造器時沒有提供參數的外部名字,Swift 會爲構造器的每一個參數自動生成一個跟內部名字相同的外部名

若是你不但願爲構造器的某個參數提供外部名字,你可使用下劃線(_)來顯式描述它的外部名

默認構造器

若是結構體或類的全部屬性都有默認值,同時沒有自定義的構造器,那麼 Swift 會給這些結構體或類提供一個默認構造器(default initializers)。這個默認構造器將簡單地建立一個全部屬性值都設置爲默認值的實例。

除了上面提到的默認構造器,若是結構體沒有提供自定義的構造器,它們將自動得到一個逐一成員構造器,即便結構體的存儲型屬性沒有默認值。

指定構造器、便利構造器

指定構造器和便利構造器的語法

類的指定構造器的寫法跟值類型簡單構造器同樣:

init(parameters) {
    statements
}

便利構造器也採用相一樣式的寫法,但須要在init關鍵字以前放置convenience關鍵字,並使用空格將它們倆分開:

convenience init(parameters) {
    statements
}

爲了簡化指定構造器和便利構造器之間的調用關係,Swift 採用如下三條規則來限制構造器之間的代理調用:

  • 指定構造器必須調用其直接父類的的指定構造器。
  • 便利構造器必須調用同一類中定義的其它構造器。
  • 便利構造器必須最終致使一個指定構造器被調用。

一個更方便記憶的方法是:指定構造器必須老是向上代理,便利構造器必須老是橫向代理

兩段式構造

Swift 的兩段式構造過程跟 Objective-C 中的構造過程相似。最主要的區別在於階段 1,Objective-C 給每個屬性賦值0或空值(好比說0或nil)。Swift 的構造流程則更加靈活,它容許你設置定製的初始值,並自如應對某些屬性不能以0或nil做爲合法默認值的狀況。

Swift 編譯器將執行 4 種有效的安全檢查,以確保兩段式構造過程能不出錯地完成:

安全檢查 1

指定構造器必須保證它所在類引入的全部屬性都必須先初始化完成,以後才能將其它構造任務向上代理給父類中的構造器。

如上所述,一個對象的內存只有在其全部存儲型屬性肯定以後才能徹底初始化。爲了知足這一規則,指定構造器必須保證它所在類引入的屬性在它往上代理以前先完成初始化。

安全檢查 2

指定構造器必須先向上代理調用父類構造器,而後再爲繼承的屬性設置新值。若是沒這麼作,指定構造器賦予的新值將被父類中的構造器所覆蓋。

安全檢查 3

便利構造器必須先代理調用同一類中的其它構造器,而後再爲任意屬性賦新值。若是沒這麼作,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋。

安全檢查 4

構造器在第一階段構造完成以前,不能調用任何實例方法,不能讀取任何實例屬性的值,不能引用self做爲一個值。

類實例在第一階段結束之前並非徹底有效的。只有第一階段完成後,該實例纔會成爲有效實例,才能訪問屬性和調用方法。

如下是兩段式構造過程當中基於上述安全檢查的構造流程展現:

階段 1

  • 某個指定構造器或便利構造器被調用。
  • 完成新實例內存的分配,但此時內存尚未被初始化。
  • 指定構造器確保其所在類引入的全部存儲型屬性都已賦初值。存儲型屬性所屬的內存完成初始化。
  • 指定構造器將調用父類的構造器,完成父類屬性的初始化。
  • 這個調用父類構造器的過程沿着構造器鏈一直往上執行,直到到達構造器鏈的最頂部。
  • 當到達了構造器鏈最頂部,且已確保全部實例包含的存儲型屬性都已經賦值,這個實例的內存被認爲已經徹底初始化。此時階段 1 完成。

階段 2

  • 從頂部構造器鏈一直往下,每一個構造器鏈中類的指定構造器都有機會進一步定製實例。構造器此時能夠訪問self、修改它的屬性並調用實例方法等等。
  • 最終,任意構造器鏈中的便利構造器能夠有機會定製實例和使用self。
  1. 調用構造器後,先完成實例內存的分配,再保證本身引入的存儲性屬性都被初始化(有默認值也可),而後調用父類的構造器(沒有設置默認值的存儲性屬性,必須在構造器初始化完成後再調用父類的指定構造器),當父類有多個指定構造器,只調用其中一個便可
  2. 存儲性屬性必須先初始化後才能使用,特別是在構造器中尤爲要注意。父類的存儲屬性,只有調用完父類的構造器以後才能用self來右值訪問,使用self來左值賦值
class Food {
    var name: String
    init(name: String) {
//        print(self.name)  // 這裏訪問會出錯,由於self.name尚未初始化
        self.name = name    // 初始化 self.name
        print(self.name)    // 已經初始化,能夠訪問
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

可失敗構造器

init?()

你能夠用非可失敗構造器重寫可失敗構造器,但反過來卻不行。

當你用子類的非可失敗構造器重寫父類的可失敗構造器時,向上代理到父類的可失敗構造器的惟一方式是對父類的可失敗構造器的返回值進行強制解包。例如:

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

類,結構體,枚舉的可失敗構造器能夠橫向代理到類型中的其餘可失敗構造器。相似的,子類的可失敗構造器也能向上代理到父類的可失敗構造器。

不管是向上代理仍是橫向代理,若是你代理到的其餘可失敗構造器觸發構造失敗,整個構造過程將當即終止,接下來的任何構造代碼不會再被執行。

可失敗構造器,能夠不向上代理

必要構造器

在類的構造器前添加required修飾符代表全部該類的子類都必須實現該構造器:

class SomeClass {
    required init() {
        // 構造器的實現代碼
    }
}

在子類重寫父類的必要構造器時,必須在子類的構造器前也添加required修飾符,代表該構造器要求也應用於繼承鏈後面的子類。在重寫父類中必要的指定構造器時,不須要添加override修飾符:

class SomeSubclass: SomeClass {
    required init() {
        // 構造器的實現代碼
    }
}

析構過程

析構器只適用於類類型,當一個類的實例被釋放以前,析構器會被當即調用。在類的定義中,每一個類最多隻能有一個析構器.

析構器是在實例釋放發生前被自動調用。你不能主動調用析構器。子類繼承了父類的析構器,而且在子類析構器實現的最後,父類的析構器會被自動調用。即便子類沒有提供本身的析構器,父類的析構器也一樣會被調用

由於直到實例的析構器被調用後,實例纔會被釋放,因此析構器能夠訪問實例的全部屬性,而且能夠根據那些屬性能夠修改它的行爲

枚舉

Swift 中的枚舉更加靈活,沒必要給每個枚舉成員提供一個值。若是給枚舉成員提供一個值(稱爲「原始」值),則該值的類型能夠是字符串,字符,或是一個整型值或浮點數。

下面是用枚舉表示指南針四個方向的例子:

enum CompassPoint {
    case North
    case South
    case East
    case West
}

與 C 和 Objective-C 不一樣,Swift 的枚舉成員在被建立時不會被賦予一個默認的整型值。在上面的CompassPoint例子中,North,South,East和West不會被隱式地賦值爲0,1,2和3。相反,這些枚舉成員自己就是完備的值,這些值的類型是已經明肯定義好的CompassPoint類型。

原始值

enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

枚舉類型ASCIIControlCharacter的原始值類型被定義爲Character,對於一個特定的枚舉成員,它的原始值始終不變

相關文章
相關標籤/搜索