之前學習swift時的個人筆記,根據github: "the swift programming language in chinese" 學習、總結,將重要的內容提取,加以理解後整理為學習筆記,方便以後查詢用。詳細可以參考 "the swift programming language in ch ...
之前學習swift時的個人筆記,根據github:the-swift-programming-language-in-chinese學習、總結,將重要的內容提取,加以理解後整理為學習筆記,方便以後查詢用。詳細可以參考the-swift-programming-language-in-chinese,或者蘋果官方英文版文檔
當前版本是swift2.2
自動引用計數
引用計數僅僅應用於類的實例。結構體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞
當你每次創建一個類的新的實例的時候,ARC 會分配一塊記憶體來儲存該實例信息。記憶體中會包含實例的類型信息,以及這個實例所有相關的存儲型屬性的值。
然而,當 ARC 收回和釋放了正在被使用中的實例,該實例的屬性和方法將不能再被訪問和調用。實際上,如果你試圖訪問這個實例,你的應用程式很可能會崩潰。
為了確保使用中的實例不會被銷毀,ARC 會跟蹤和計算每一個實例正在被多少屬性,常量和變數所引用。哪怕實例的引用數為1,ARC都不會銷毀這個實例。
為了使上述成為可能,無論你將實例賦值給屬性、常量或變數,它們都會創建此實例的強引用。之所以稱之為“強”引用,是因為它會將實例牢牢地保持住,只要強引用還在,實例是不允許被銷毀的。
弱引用必須被聲明為變數,表明其值能在運行時被修改。弱引用不能被聲明為常量。
在使用垃圾收集的系統里,弱指針有時用來實現簡單的緩衝機制,因為沒有強引用的對象只會在記憶體壓力觸發垃圾收集時才被銷毀。但是在 ARC 中,一旦值的最後一個強引用被移除,就會被立即銷毀,這導致弱引用並不適合上面的用途。即,ARC中weak對象不會被緩衝,會立即釋放
無主引用
和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用是永遠有值的。因此,無主引用總是被定義為非可選類型(non-optional type)。你可以在聲明屬性或者變數時,在前面加上關鍵字unowned表示這是一個無主引用。
如果你試圖在實例被銷毀後,訪問該實例的無主引用,會觸發運行時錯誤。使用無主引用,你必須確保引用始終指向一個未銷毀的實例。
還需要註意的是如果你試圖訪問實例已經被銷毀的無主引用,Swift 確保程式會直接崩潰,而不會發生無法預期的行為。所以你應當避免這樣的事情發生。
可選鏈式調用
調用結果返回一個可選值
可選鏈式調用(Optional Chaining)是一種可以在當前值可能為nil的可選值上請求和調用屬性、方法及下標的方法。如果可選值有值,那麼調用就會成功;如果可選值是nil,那麼調用將返回nil。多個調用可以連接在一起形成一個調用鏈,如果其中任何一個節點為nil,整個調用鏈都會失敗,即返回nil
Swift 的可選鏈式調用和 Objective-C 中向nil發送消息有些相像,但是 Swift 的可選鏈式調用可以應用於任意類型,並且能檢查調用是否成功
給可選鏈式調用賦值時,如果左邊為nil,則右邊的不會執行,比如john.residence?.address = createAddress(),如果residence為nil則createAddress函數不會被執行
通過可選鏈式調用調用方法
一個沒有返回值的方法具有隱式的返回類型Void。這意味著沒有返回值的方法也會返回(),或者說空的元組。如果在可選值上通過可選鏈式調用來調用這個方法,該方法的返回類型會是Void?,而不是Void,因為通過可選鏈式調用得到的返回值都是可選的。
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
同樣的,可以據此判斷通過可選鏈式調用為屬性賦值是否成功
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
- 如果你訪問的值不是可選的,可選鏈式調用將會返回可選值。
- 如果你訪問的值就是可選的,可選鏈式調用不會讓可選返回值變得“更可選”。
因此:
- 通過可選鏈式調用訪問一個Int值,將會返回Int?,無論使用了多少層可選鏈式調用。
- 類似的,通過可選鏈式調用訪問Int?值,依舊會返回Int?值,並不會返回Int??。
錯誤處理
用 throwing 函數傳遞錯誤
為了表示一個函數、方法或構造器可以拋出錯誤,在函數聲明的參數列表之後加上throws關鍵字。一個標有throws關鍵字的函數被稱作throwing 函數。如果這個函數指明瞭返回值類型,throws關鍵詞需要寫在箭頭(->)的前面。
func canThrowErrors() throws -> String
指定清理操作
可以使用defer語句在即將離開當前代碼塊時執行一系列語句。該語句讓你能執行一些必要的清理工作,不管是以何種方式離開當前代碼塊的——無論是由於拋出錯誤而離開,還是由於諸如return或者break的語句。例如,你可以用defer語句來確保文件描述符得以關閉,以及手動分配的記憶體得以釋放。
defer語句將代碼的執行延遲到當前的作用域退出之前。該語句由defer關鍵字和要被延遲執行的語句組成。延遲執行的語句不能包含任何控制轉移語句,例如break或是return語句,或是拋出一個錯誤。延遲執行的操作會按照它們被指定時的順序的相反順序執行——也就是說,第一條defer語句中的代碼會在第二條defer語句中的代碼被執行之後才執行,以此類推。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件。
}
// close(file) 會在這裡被調用,即作用域的最後。
}
}
上面的代碼使用一條defer語句來確保open(:)函數有一個相應的對close(:)函數的調用。
類型轉換
檢查類型(Checking Type)
用類型檢查操作符(is)來檢查一個實例是否屬於特定子類型。若實例屬於那個子類型,類型檢查操作符返回 true,否則返回 false。
向下轉型(Downcasting)
某類型的一個常量或變數可能在幕後實際上屬於一個子類。當確定是這種情況時,你可以嘗試向下轉到它的子類型,用類型轉換操作符(as? 或 as!)。
當你不確定向下轉型可以成功時,用類型轉換的條件形式(as?)。條件形式的類型轉換總是返回一個可選值(optional value),並且若下轉是不可能的,可選值將是 nil。這使你能夠檢查向下轉型是否成功。
只有你可以確定向下轉型一定會成功時,才使用強制形式(as!)。當你試圖向下轉型為一個不正確的類型時,強制形式的類型轉換會觸發一個運行時錯誤。
Any 和 AnyObject 的類型轉換
Swift 為不確定類型提供了兩種特殊的類型別名:
AnyObject 可以表示任何類類型的實例。
Any 可以表示任何類型,包括函數類型。
泛型(Generics)
泛型函數
泛型函數可以適用於任何類型,下麵的 swapTwoValues(_:_:)
函數是上面三個函數的泛型版本:
func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}
另外一個不同之處在於這個泛型函數名(swapTwoValues(_:_:))
後面跟著占位類型名(T),並用尖括弧括起來(swapTwoValues(_:_:)
函數定義內的一個占位類型名,因此 Swift 不會去查找名為 T 的實際類型。
命名類型參數
在大多數情況下,類型參數具有一個描述性名字,例如 DictionaryswapTwoValues(_:_:)
函數中的 T 一樣。
- 泛型實現棧
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
你可以通過在尖括弧中寫出棧中需要存儲的數據類型來創建並初始化一個 Stack 實例。例如,要創建一個 String 類型的棧,可以寫成 Stack
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 棧中現在有 4 個字元串
擴展一個泛型類型
當你擴展一個泛型類型的時候,你並不需要在擴展的定義中提供類型參數列表。原始類型定義中聲明的類型參數列表在擴展中可以直接使用,並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用。
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
類型約束語法
你可以在一個類型參數名後面放置一個類名或者協議名,並用冒號進行分隔,來定義類型約束,它們將成為類型參數列表的一部分。對泛型函數添加類型約束的基本語法如下所示(作用於泛型類型時的語法與之相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這裡是泛型函數的函數體部分
}
上面這個函數有兩個類型參數。第一個類型參數 T,有一個要求 T 必須是 SomeClass 子類的類型約束;第二個類型參數 U,有一個要求 U 必須符合 SomeProtocol 協議的類型約束。
訪問控制(Access Control)
訪問級別
Swift 為代碼中的實體提供了三種不同的訪問級別。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關。
- public:可以訪問同一模塊源文件中的任何實體,在模塊外也可以通過導入該模塊來訪問源文件里的所有實體。通常情況下,框架中的某個介面可以被任何人使用時,你可以將其設置為 public 級別。
- internal:可以訪問同一模塊源文件中的任何實體,但是不能從模塊外訪問該模塊源文件中的實體。通常情況下,某個介面只在應用程式或框架內部使用時,你可以將其設置為 internal 級別。
- private:限制實體只能在所在的源文件內部使用。使用 private 級別可以隱藏某些功能的實現細節。
public 為最高(限制最少)訪問級別,private 為最低(限制最多)訪問級別。預設為internal
一個類型的訪問級別也會影響到類型成員(屬性、方法、構造器、下標)的預設訪問級別。如果你將類型指定為 private 級別,那麼該類型的所有成員的預設訪問級別也會變成 private。如果你將類型指定為 public 或者 internal 級別(或者不明確指定訪問級別,而使用預設的 internal 訪問級別),那麼該類型的所有成員的預設訪問級別將是 internal。
上面提到,一個 public 類型的所有成員的訪問級別預設為 internal 級別,而不是 public 級別。如果你想將某個成員指定為 public 級別,那麼你必須顯式指定。這樣做的好處是,在你定義公共介面的時候,可以明確地選擇哪些介面是需要公開的,哪些是內部使用的,避免不小心將內部使用的介面公開。
訪問級別基本原則
Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更高的實體。
例如:
- 一個 public 訪問級別的變數,其類型的訪問級別不能是 internal 或 private。因為無法保證變數的類型在使用變數的地方也具有訪問許可權。
- 函數的訪問級別不能高於它的參數類型和返回類型的訪問級別。因為如果函數定義為 public 而參數類型或者返回類型定義為 internal 或 private,就會出現函數可以在任何地方被訪問,但是它的參數類型和返回類型卻不可以。
框架的訪問級別
當你開發框架時,就需要把一些對外的介面定義為 public 級別,以便使用者導入該框架後可以正常使用其功能。這些被你定義為 public 的介面,就是這個框架的 API。
單元測試 target 的訪問級別
當你的應用程式包含單元測試 target 時,為了測試,測試模塊需要訪問應用程式模塊中的代碼。預設情況下只有 public 級別的實體才可以被其他模塊訪問。然而,如果在導入應用程式模塊的語句前使用 @testable 特性,然後在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程式模塊,單元測試 target 就可以訪問應用程式模塊中所有 internal 級別的實體。
元組類型
元組的訪問級別將由元組中訪問級別最嚴格的類型來決定。例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為 internal 級別,另一個類型為 private 級別,那麼這個元組的訪問級別為 private。
元組不同於類、結構體、枚舉、函數那樣有單獨的定義。元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。
函數類型
函數的訪問級別根據訪問級別最嚴格的參數類型或返回類型的訪問級別來決定。如果這種推斷出來的訪問級別不符合函數定義所在環境的預設訪問級別,那麼就需要明確地指定該函數的訪問級別。而且函數訪問級別不能高於訪問的參數
枚舉類型
枚舉成員的訪問級別和該枚舉類型相同,你不能為枚舉成員單獨指定不同的訪問級別。
- 原始值和關聯值
枚舉定義中的任何原始值或關聯值的類型的訪問級別至少不能低於枚舉類型的訪問級別。例如,你不能在一個 internal 訪問級別的枚舉中定義 private 級別的原始值類型。
嵌套類型
如果在 private 級別的類型中定義嵌套類型,那麼該嵌套類型就自動擁有 private 訪問級別。如果在 public 或者 internal 級別的類型中定義嵌套類型,那麼該嵌套類型自動擁有 internal 訪問級別。如果想讓嵌套類型擁有 public 訪問級別,那麼需要明確指定該嵌套類型的訪問級別。
子類
子類的訪問級別不得高於父類的訪問級別。例如,父類的訪問級別是 internal,子類的訪問級別就不能是 public。
Getter 和 Setter
常量、變數、屬性、下標的 Getters 和 Setters 的訪問級別和它們所屬類型的訪問級別相同。
構造器
自定義構造器的訪問級別可以低於或等於其所屬類型的訪問級別。唯一的例外是必要構造器,它的訪問級別必須和所屬類型的訪問級別相同。
如同函數或方法的參數,構造器參數的訪問級別也不能低於構造器本身的訪問級別。(要麼改函數,要麼改參數的訪問級別)
預設構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public。如果一個類型被指定為 public 級別,那麼預設構造器的訪問級別將為 internal
協議
協議中的每一個要求都具有和該協議相同的訪問級別。你不能將協議中的要求設置為其他訪問級別。這樣才能確保該協議的所有要求對於任意採納者都將可用。
如果你定義了一個 public 訪問級別的協議,那麼該協議的所有實現也會是 public 訪問級別
協議一致性
一個類型可以採納比自身訪問級別低的協議。例如,你可以定義一個 public 級別的類型,它可以在其他模塊中使用,同時它也可以採納一個 internal 級別的協議,但是只能在該協議所在的模塊中作為符合該協議的類型使用。
採納了協議的類型的訪問級別取它本身和所採納協議兩者間最低的訪問級別。也就是說如果一個類型是 public 級別,採納的協議是 internal 級別,那麼採納了這個協議後,該類型作為符合協議的類型時,其訪問級別也是 internal。
如果你採納了協議,那麼實現了協���的所有要求後,你必須確保這些實現的訪問級別不能低於協議的訪問級別。例如,一個 public 級別的類型,採納了 internal 級別的協議,那麼協議的實現至少也得是 internal 級別。
Swift 和 Objective-C 一樣,協議的一致性是全局的,也就是說,在同一程式中,一個類型不可能用兩種不同的方式實現同一個協議。
擴展
你可以在訪問級別允許的情況下對類、結構體、枚舉進行擴展。擴展成員具有和原始類型成員一致的訪問級別。例如,你擴展了一個 public 或者 internal 類型,擴展中的成員具有預設的 internal 訪問級別,和原始類型中的成員一致 。如果你擴展了一個 private 類型,擴展成員則擁有預設的 private 訪問級別。
或者,你可以明確指定擴展的訪問級別(例如,private extension),從而給該擴展中的所有成員指定一個新的預設訪問級別。這個新的預設訪問級別仍然可以被單獨指定的訪問級別所覆蓋。
通過擴展添加協議一致性
如果你通過擴展來採納協議,那麼你就不能顯式指定該擴展的訪問級別了。協議擁有相應的訪問級別,並會為該擴展中所有協議要求的實現提供預設的訪問級別。
泛型
泛型類型或泛型函數的訪問級別取決於泛型類型或泛型函數本身的訪問級別,還需結合類型參數的類型約束的訪問級別,根據這些訪問級別中的最低訪問級別來確定。
類型別名
你定義的任何類型別名都會被當作不同的類型,以便於進行訪問控制。類型別名的訪問級別不可高於其表示的類型的訪問級別。例如,private 級別的類型別名可以作為 public、internal、private 類型的別名,但是 public 級別的類型別名只能作為 public 類型的別名,不能作為 internal 或 private 類型的別名。
高級運算符(Advanced Operators)
與 C 語言中的算術運算符不同,Swift 中的算術運算符預設是不會溢出的。所有溢出行為都會被捕獲並報告為錯誤。如果想讓系統允許溢出行為,可以選擇使用 Swift 中另一套預設支持溢出的運算符,比如溢出加法運算符(&+)。所有的這些溢出運算符都是以 & 開頭的。
對無符號整數進行移位的規則如下:
- 已經存在的位按指定的位數進行左移和右移。
- 任何因移動而超出整型存儲範圍的位都會被丟棄。
- 用 0 來填充移位後產生的空白位。
有符號
- 當對整數進行按位右移運算時,遵循與無符號整數相同的規則,但是對於移位產生的空白位使用符號位進行填充,而不是用 0。
運算符函數(運算符重載)
單目運算符只運算一個值。當運算符出現在值之前時,它就是首碼的(例如 -a),而當它出現在值之後時,它就是尾碼的(例如 b!) a+b,+為中綴運算符
不能對預設的賦值運算符(=)進行重載。只有組合賦值運算符可以被重載。同樣地,也無法對三目條件運算符 (a ? b : c) 進行重載。
等價運算符
自定義的類和結構體沒有對等價運算符進行預設實現,等價運算符通常被稱為“相等”運算符(==)與“不等”運算符(!=)。對於自定義類型,Swift 無法判斷其是否“相等”,因為“相等”的含義取決於這些自定義類型在你的代碼中所扮演的角色。
自定義運算符
除了實現標準運算符,在 Swift 中還可以聲明和實現自定義運算符。可以用來自定義運算符的字元列表請參考運算符。
新的運算符要使用 operator 關鍵字在全局作用域內進行定義,同時還要指定 prefix、infix 或者 postfix 修飾符:
prefix operator +++ {}