Swift5總結(jié)
關(guān)于Swift
Swift通過采用現(xiàn)代編程模式來定義大多數(shù)常見的編程錯誤:
- 變量在使用前始終初始化平窘。
- 檢查數(shù)組索引是否存在越界錯誤蝎亚。
- 檢查整數(shù)是否溢出狠怨。
- Optionals確保nil明確處理值却妨。
- 內(nèi)存自動管理哑梳。
- 錯誤處理允許從意外故障中受控恢復(fù)耻陕。
Swift初見
簡單的值
常量或者變量的類型必須和賦給它們的值一樣拙徽。然而,不用明確地聲明類型诗宣。當(dāng)通過一個值來聲明變量和常量時膘怕,編譯器會自動推斷其類型。
如果初始值沒有提供足夠的信息(或者沒有初始值)召庞,那需要在變量后面聲明類型岛心,用冒號分割来破。
值永遠不會隱式轉(zhuǎn)換為其他類型。如果需要將值轉(zhuǎn)換為其他類型忘古,請顯式創(chuàng)建所需類型的實例徘禁。
處理可選值:
- 使用if-let、guard-let解包
- 使用??運算符提供默認值
- 使用!運算符強制解包
在操作(比如方法髓堪、屬性和子腳本)之前加 ?送朱。如果 ? 之前的值是 nil,? 后面的東西都會被忽略干旁,并且整個表達式返回 nil
switch 語句中匹配到的 case 語句之后驶沼,就會退出 switch 語句,并不會繼續(xù)向下運行争群,所以不需要在每個子句結(jié)尾寫 break回怜。
字典是無序集合,迭代字典的鍵和值時順序不確定祭阀。
閉包
簡寫閉包
- 已知類型的閉包可以省略參數(shù)類型和返回類型
- 單一語句隱式返回該條語句的值可以省略return關(guān)鍵字
- 可以使用($+編號)來引用閉包的參數(shù)
- 當(dāng)一個閉包作為最后一個參數(shù)傳給一個函數(shù)的時候鹉戚,它可以直接跟在圓括號后面。
- 當(dāng)閉包是函數(shù)的唯一參數(shù)時专控,可以省略圓括號
對象和類
不需要計算屬性抹凳,但是仍然需要在設(shè)置一個新值之前或者之后運行代碼,使用 willSet 和 didSet伦腐。寫入的代碼會在屬性值發(fā)生改變時調(diào)用柏蘑,但不包含構(gòu)造器中發(fā)生值改變的情況又官。
處理變量的可選值時,可以在操作(比如方法眯亦、屬性和子腳本)之前加 ?。如果 ? 之前的值是 nil虏等,? 后面的東西都會被忽略逛揩,并且整個表達式返回 nil并级。否則,可選值會被解包侮腹,之后的所有代碼都會按照解包后的值運行嘲碧。在這兩種情況下,整個表達式的值也是一個可選值父阻。
枚舉和結(jié)構(gòu)體
如果枚舉成員的實例有原始值愈涩,那么這些值是在聲明的時候就已經(jīng)決定了,這意味著不同枚舉實例的枚舉成員總會有一個相同的原始值加矛。當(dāng)然我們也可以為枚舉成員設(shè)定關(guān)聯(lián)值履婉,關(guān)聯(lián)值是在創(chuàng)建實例時決定的。這意味著同一枚舉成員不同實例的關(guān)聯(lián)值可以不相同斟览』偻龋可以把關(guān)聯(lián)值想象成枚舉成員實例的存儲屬性。
協(xié)議和擴展
mutating 關(guān)鍵字用來標記一個會修改結(jié)構(gòu)體的方法苛茂。類的聲明不需要標記任何方法已烤,因為類中的方法通常可以修改類屬性(類的性質(zhì))妓羊。
可以像使用其他命名類型一樣使用協(xié)議名胯究,但是當(dāng)處理類型是協(xié)議的值時,協(xié)議外定義的方法不可用躁绸。
錯誤處理
使用 throw 來拋出一個錯誤和使用 throws 來表示一個可以拋出錯誤的函數(shù)唐片。
defer 代碼塊來表示在函數(shù)返回前,函數(shù)中最后執(zhí)行的代碼涨颜。無論函數(shù)是否會拋出錯誤费韭,這段代碼都將執(zhí)行。
泛型
在類型名后面使用 where 來指定對類型的一系列需求庭瑰。
用try?將結(jié)果轉(zhuǎn)換為可選的星持。如果函數(shù)拋出錯誤,則丟棄特定錯誤弹灭,結(jié)果為nil督暂。否則,結(jié)果是一個包含函數(shù)返回的值的可選值穷吮。
基礎(chǔ)
Swift和OC的比較:
- 包含C和OC的基礎(chǔ)數(shù)據(jù)類型
- 常量更加強大方便
- 提供高階數(shù)據(jù)類型元組可以創(chuàng)建或者傳遞一組數(shù)據(jù)
- 提供可選(Optional)類型處理值缺失的情況逻翁,比nil更具有表現(xiàn)力
- 類型安全,清楚的知道值的類型
常量和變量
一般來說很少需要寫類型注解捡鱼。如果在聲明常量或者變量的時候賦了一個初始值八回,Swift 可以推斷出這個常量或者變量的類型。
分號
在同一行內(nèi)寫多條獨立的語句的情況下必須要用分號。
類型安全和類型推斷
- 類型安全的語言可以清楚地知道代碼要處理的值的類型缠诅。
- 編譯代碼時進行類型檢查(type checks)溶浴,并把不匹配的類型標記為錯誤。
因為有類型推斷管引,和 C 或者 Objective-C 比起來 Swift 很少需要聲明類型士败。
數(shù)值類型轉(zhuǎn)換
結(jié)合數(shù)字類常量和變量不同于結(jié)合數(shù)字類字面量。字面量 3 可以直接和字面量 0.14159 相加褥伴,因為數(shù)字字面量本身沒有明確的類型谅将。它們的類型只在編譯器需要求值的時候被推測。
類型別名
類型別名(type aliases)就是給現(xiàn)有類型定義另一個名字重慢∠纷裕可以使用 typealias 關(guān)鍵字來定義類型別名。
可選值解包
- 使用
!
強制解包 - 可選綁定伤锚,在 if 擅笔、guard 和 while 語句中如果有值就把值賦給一個臨時常量或者變量
- 把想要用作可選的類型的后面的問號(String?)改成感嘆號(String!)來聲明一個隱式解析可選類型(在隱式解包可選類型沒有值的時候嘗試取值,會觸發(fā)運行時錯誤屯援。和在沒有值的普通可選類型后面加一個驚嘆號一樣)
Swift 的 nil 和 Objective-C 中的 nil 并不一樣猛们。在 Objective-C 中,nil 是一個指向不存在對象的指針狞洋。在 Swift 中弯淘,nil 不是指針——它是一個確定的值,用來表示值缺失吉懊。任何類型的可選狀態(tài)都可以被設(shè)置為 nil庐橙,不只是對象類型。
錯誤處理
通過在函數(shù)的聲明中包含關(guān)鍵字throws來表示它可以引發(fā)錯誤借嗽,throw 關(guān)鍵詞在函數(shù)中拋出錯誤态鳖。當(dāng)調(diào)用可以拋出錯誤的函數(shù)時,將try關(guān)鍵字添加到表達式中恶导。
一個 do 語句創(chuàng)建了一個新的包含作用域浆竭,使得錯誤能被傳播到一個或多個 catch 從句。
斷言和先決條件
斷言和先決條件可以強制檢查數(shù)據(jù)和程序狀態(tài)惨寿,使得程序可預(yù)測的中止(不是系統(tǒng)強制的邦泄,被動的中止),并幫助使這個問題更容易調(diào)試裂垦。
- 斷言僅在調(diào)試環(huán)境運行顺囊,在生產(chǎn)環(huán)境中,斷言的條件將不會進行計算蕉拢。這個意味著在開發(fā)階段可以使用很多斷言特碳,但是這些斷言在生產(chǎn)環(huán)境中不會產(chǎn)生任何影響诚亚。
- 先決條件則在調(diào)試環(huán)境和生產(chǎn)環(huán)境中運行。
使用 unchecked 模式(-Ounchecked)編譯代碼测萎,先決條件將不會進行檢查,編譯器假設(shè)所有的先決條件總是為 true(真)届巩。fatalError(_:file:line:) 函數(shù)總是中斷執(zhí)行硅瞧,無論怎么進行優(yōu)化設(shè)定。
基本運算符
與C和Objective-C中的賦值運算符不同恕汇,Swift中的賦值運算符本身不返回值腕唧。通過使if x = y無效,Swift可以幫助避免代碼中的這類錯誤瘾英。
元組的比較從左到右進行枣接,一次一個值,直到比較找到兩個不相等的值缺谴。Swift標準庫包含的元組比較運算符但惶,元組必須少于7個元素。要將七個或更多元素的元組進行比較湿蛔,您必須自己實現(xiàn)比較運算符膀曾。
空合運算符(a ?? b)如果可選值a包含一個值則進行解包,如果是nil返回一個默認b值阳啥。表達式a始終是可選類型添谊。表達式b必須與存儲在a中的類型匹配。
字符和字符串
多行字符串字面量 - 由三個雙引號括起來的字符序列察迟。
- 多行字符串字面量中斩狱,如果想換行,以便加強代碼的可讀性扎瓶,但是又不想在多行字符串字面量中出現(xiàn)換行符的話所踊,在這些行的末尾寫一個反斜杠(\)作為續(xù)行符。
- 要創(chuàng)建以換行符開頭或結(jié)尾的多行字符串字面量概荷,請將空行寫為第一行或最后一行污筷。
- 可以縮進多行字符串。在右引號標記(""")之前的空格告訴Swift在所有其他行之前要忽略那些空格(如果有行的縮進空格不夠會報錯)乍赫。
將字符串放在引號(")中并用數(shù)字符號(#)括起來瓣蛀。例如,打印字符串文字 #"Line 1 \nLine 2"# 會打印換行符轉(zhuǎn)義序列(\n)而不是給文字換行雷厂。
如果需要字符串文字中字符的特殊效果惋增,請匹配轉(zhuǎn)義字符(\)后面添加與起始位置個數(shù)相匹配的 # 符。 例如改鲫,如果字符串是 #"Line 1 \nLine 2"# 并且想要換行诈皿,則可以使用 #"Line 1 #nLine 2"# 來代替林束。
- 字符串可以通過加法運算符(+)相加在一起(或稱“連接”)創(chuàng)建一個新的字符串
- 也可以通過加法賦值運算符(+=)將一個字符串添加到一個已經(jīng)存在字符串變量上
- 可以用 append() 方法將一個字符附加到一個字符串變量的尾部
插值字符串中寫在括號中的表達式不能包含非轉(zhuǎn)義反斜杠(\),并且不能包含回車或換行符稽亏。不過壶冒,插值字符串可以包含其他字面量。
可擴展的字形群可以由多個 Unicode 標量組成截歉,使用可拓展的字符群集作為 Character 值來連接或改變字符串時胖腾,并不一定會更改字符串的字符數(shù)量。
count 屬性必須遍歷全部的 Unicode 標量瘪松,來確定字符串的字符數(shù)量咸作。
通過 count 屬性返回的字符數(shù)量并不總是與包含相同字符的 NSString 的 length 屬性相同。NSString 的 length 屬性是利用 UTF-16 表示的十六位代碼單元數(shù)字宵睦,而不是 Unicode 可擴展的字符群集记罚。
可以使用 startIndex 和 endIndex 屬性或者 index(before:) 、index(after:) 和 index(_:offsetBy:) 方法操作字符串的索引壳嚎。
endIndex 屬性不能作為一個字符串的有效下標桐智。如果 String 是空串,startIndex 和 endIndex 是相等的烟馅。
獲取越界索引對應(yīng)的 Character酵使,將引發(fā)一個運行時錯誤。
使用 insert(:at:)焙糟、insert(contentsOf:at:)口渔、remove(at:) 和 removeSubrange(:) 方法操作字符串。
String 和 SubString 的區(qū)別在于性能優(yōu)化上穿撮,SubString 可以重用原 String 的內(nèi)存空間缺脉,或者另一個 SubString 的內(nèi)存空間(String 也有同樣的優(yōu)化,但如果兩個 String 共享內(nèi)存的話悦穿,它們就會相等)攻礼。這一優(yōu)化意味著在修改 String 和 SubString 之前都不需要消耗性能去復(fù)制內(nèi)存。
只要可擴展的字形群集有同樣的語言意義和外觀則認為它們標準相等栗柒,即使它們是由不同的 Unicode 標量構(gòu)成礁扮。
集合類型
Swift 中的數(shù)組、集合和字典必須明確其中保存的鍵和值類型瞬沦,這樣就可以避免插入一個錯誤數(shù)據(jù)類型的值太伊。同理,對于獲取到的值也可以放心逛钻,其數(shù)據(jù)類型是確定的僚焦。Swift 的數(shù)組、集合和字典類型被實現(xiàn)為泛型曙痘。
如果把數(shù)組芳悲、集合或字典分配成常量立肘,那么它就是不可變的,它的大小和內(nèi)容都不能被改變名扛。
相同的值可以多次出現(xiàn)在一個數(shù)組的不同位置中谅年。Swift 的 Array 類型被橋接到 Foundation 中的 NSArray 類。
數(shù)組的enumerated()方法返回一個由索引值和數(shù)據(jù)值組成的元組數(shù)組肮韧。
當(dāng)集合元素順序不重要時或者希望確保每個元素只出現(xiàn)一次時可以使用集合而不是數(shù)組融蹂。和數(shù)組不同的是,集合沒有等價的簡化形式惹苗。
一個集合類型不能從數(shù)組字面量中被直接推斷出來殿较,因此 Set 類型必須顯式聲明耸峭。然而桩蓉,由于 Swift 的類型推斷功能,如果想使用一個數(shù)組字面量構(gòu)造一個集合并且與該數(shù)組字面量中的所有元素類型相同劳闹,那么無須寫出集合(元素)的具體類型院究。
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
Swift 的所有基本類型(比如 String、Int本涕、Double 和 Bool)默認都是可哈弦堤化的,可以作為集合值的類型或者字典鍵的類型菩颖。沒有關(guān)聯(lián)值的枚舉成員值(在 枚舉 有講述)默認也是可哈涎幔化的。
一個集合類型不能從數(shù)組字面量中被直接推斷出來晦闰,因此 Set 類型必須顯式聲明放祟。然而,由于 Swift 的類型推斷功能呻右,如果想使用一個數(shù)組字面量構(gòu)造一個集合并且與該數(shù)組字面量中的所有元素類型相同跪妥,那么無須寫出集合的具體類型。
Set 類型沒有確定的順序声滥,為了按照特定順序來遍歷一個集合中的值可以使用 sorted() 方法眉撵,它將返回一個有序數(shù)組,這個數(shù)組的元素排列順序由操作符 < 對元素進行比較的結(jié)果來確定落塑。
intersection(_:) 方法根據(jù)兩個集合的交集創(chuàng)建一個新的集合纽疟。
symmetricDifference(_:) 方法根據(jù)兩個集合不相交的值創(chuàng)建一個新的集合。
union(_:) 方法根據(jù)兩個集合的所有值創(chuàng)建一個新的集合憾赁。
subtracting(_:) 方法根據(jù)不在另一個集合中的值創(chuàng)建一個新的集合仰挣。
“是否相等”運算符(==)來判斷兩個集合包含的值是否全部相同。
isSubset(of:) 方法來判斷一個集合中的所有值是否也被包含在另外一個集合中缠沈。
isSuperset(of:) 方法來判斷一個集合是否包含另一個集合中所有的值膘壶。
isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法來判斷一個集合是否是另外一個集合的子集合或者父集合并且兩個集合并不相等错蝴。
isDisjoint(with:) 方法來判斷兩個集合是否不含有相同的值(是否沒有交集)。
字典是一種無序的集合颓芭,它存儲的是鍵值對之間的關(guān)系顷锰,其所有鍵的值需要是相同的類型,所有值的類型也需要相同亡问。一個字典的 Key 類型必須遵循 Hashable 協(xié)議闷尿,就像 Set 的值類型。
字典的updateValue(_:forKey:) 方法會返回對應(yīng)值類型的可選類型微谓。
Swift 的 Dictionary 是無序集合類型憨栽。為了以特定的順序遍歷字典的鍵或值,可以對字典的 keys 或 values 屬性使用 sorted() 方法床玻。
控制流
使用 stride(from:to:by:) 函數(shù)跳過不需要的標記毁涉。可以在閉區(qū)間使用 stride(from:through:by:) 起到同樣作用锈死。
當(dāng)一個 switch 分支僅僅包含注釋時贫堰,會被報編譯時錯誤。注釋不是代碼語句而且也不能讓 switch 分支達到被忽略的效果待牵。應(yīng)該使用 break 來忽略某個分支其屏。
fallthrough 關(guān)鍵字不會檢查它下一個將會落入執(zhí)行的 case 中的匹配條件。fallthrough 簡單地使代碼繼續(xù)連接到下一個 case 中的代碼缨该,這和 C 語言標準中的 switch 語句特性是一樣的偎行。
使用標簽(statement label)來標記一個循環(huán)體或者條件語句,對于一個條件語句贰拿,可以使用 break 加標簽的方式蛤袒,來結(jié)束這個被標記的語句。對于一個循環(huán)語句壮不,可以使用 break 或者 continue 加標簽汗盘,來結(jié)束或者繼續(xù)這條被標記語句的執(zhí)行。
不同于 if 語句询一,一個 guard 語句總是有一個 else 從句隐孽,如果條件不為真則執(zhí)行 else 從句中的代碼。
guard 語句的 else 分支必須轉(zhuǎn)移控制以退出 guard 語句出現(xiàn)的代碼段健蕊×庹螅可以用控制轉(zhuǎn)移語句如 return、break缩功、continue 或者 throw 做這件事晴及,或者調(diào)用一個不返回的方法或函數(shù),例如 fatalError()嫡锌。
相比于可以實現(xiàn)同樣功能的 if 語句虑稼,按需使用 guard 語句會提升我們代碼的可讀性琳钉。它可以使代碼連貫的被執(zhí)行而不需要將它包在 else 塊中,它可以使在緊鄰條件判斷的地方蛛倦,處理違規(guī)的情況歌懒。
函數(shù)
輸入輸出參數(shù)不能有默認值,而且可變參數(shù)不能用 inout 標記溯壶。
閉包
隱式返回單表達式閉包及皂,即單表達式閉包可以省略 return 關(guān)鍵字
尾隨閉包改寫在方法圓括號的外面,如果閉包表達式是函數(shù)或方法的唯一參數(shù)且改,當(dāng)使用尾隨閉包時验烧,可以把 () 省略掉。
尾隨閉包語法又跛,優(yōu)雅地在函數(shù)后封裝了閉包的具體功能碍拆,而不再需要將整個閉包包裹在 map(_:) 方法的括號內(nèi)。
逃逸閉包效扫,必須顯式地引用 self倔监;非逃逸閉包直砂,可以隱式引用 self菌仁。
自動閉包是一種自動創(chuàng)建的閉包,用于包裝傳遞給函數(shù)作為參數(shù)的表達式静暂。這種閉包不接受任何參數(shù)济丘,當(dāng)它被調(diào)用的時候,會返回被包裝在其中的表達式的值洽蛀。這種便利語法能夠省略閉包的花括號摹迷,用一個普通的表達式來代替顯式的閉包。函數(shù)通過將參數(shù)標記為 @autoclosure 來接收一個自動閉包郊供。
自動閉包能夠延遲求值峡碉,因為直到調(diào)用這個閉包,代碼段才會被執(zhí)行驮审。延遲求值對于那些有副作用(Side Effect)和高計算成本的代碼來說是很有益處的鲫寄,因為它使得能控制代碼的執(zhí)行時機。
枚舉
枚舉定義了一個全新的類型疯淫,像 Swift 中其他類型一樣地来。
令枚舉遵循 CaseIterable 協(xié)議。Swift 會生成一個 allCases 屬性熙掺,用于表示一個包含枚舉所有成員的集合未斑。有關(guān)聯(lián)值或原始值的枚舉不能遵守 CaseIterable 協(xié)議
如果一個枚舉成員的所有關(guān)聯(lián)值都被提取為常量,或者都被提取為變量币绩,為了簡潔蜡秽,可以只在成員名稱前標注一個 let 或者 var府阀。
原始值和關(guān)聯(lián)值是不同的。原始值是在定義枚舉時被預(yù)先填充的值芽突。對于一個特定的枚舉成員肌似,它的原始值始終不變。關(guān)聯(lián)值是創(chuàng)建一個基于枚舉成員的常量或變量時才設(shè)置的值诉瓦,枚舉成員的關(guān)聯(lián)值可以變化川队。
在枚舉類型開頭加上 indirect 關(guān)鍵字來表明它的所有成員都是可遞歸的。
結(jié)構(gòu)體和類
Swift 中結(jié)構(gòu)體和類有很多共同點睬澡。兩者都可以:
- 定義屬性用于存儲值
- 定義方法用于提供功能
- 定義下標操作用于通過下標語法訪問它們的值
- 定義構(gòu)造器用于設(shè)置初始值
- 通過擴展以增加默認實現(xiàn)之外的功能
- 遵循協(xié)議以提供某種標準功能
與結(jié)構(gòu)體相比固额,類還有如下的附加功能:
- 繼承允許一個類繼承另一個類的特征
- 類型轉(zhuǎn)換允許在運行時檢查和解釋一個類實例的類型
- 析構(gòu)器允許一個類實例釋放任何其所被分配的資源
- 引用計數(shù)允許對一個類的多次引用
類支持的附加功能是以增加復(fù)雜性為代價的。作為一般準則煞聪,優(yōu)先使用結(jié)構(gòu)體斗躏,因為它們更容易理解,僅在適當(dāng)或必要時才使用類昔脯。實際上啄糙,這意味著大多數(shù)自定義數(shù)據(jù)類型都會是結(jié)構(gòu)體和枚舉。
結(jié)構(gòu)體都有一個自動生成的成員逐一構(gòu)造器云稚,用于初始化新結(jié)構(gòu)體實例中成員的屬性隧饼。類沒有默認的成員逐一構(gòu)造器。
Swift 中所有的結(jié)構(gòu)體和枚舉類型都是值類型静陈。這意味著它們的實例燕雁,以及實例中所包含的任何值類型的屬性,在代碼中傳遞的時候都會被復(fù)制鲸拥。
標準庫定義的集合拐格,例如數(shù)組,字典和字符串刑赶,都對復(fù)制進行了優(yōu)化以降低性能成本捏浊。新集合不會立即復(fù)制,而是跟原集合共享同一份內(nèi)存撞叨,共享同樣的元素金踪。在集合的某個副本要被修改前,才會復(fù)制它的元素谒所。而在代碼中看起來就像是立即發(fā)生了復(fù)制热康。
屬性
結(jié)構(gòu)體實例賦值給一個常量,則無法修改該實例的任何屬性劣领,即使被聲明為可變屬性也不行姐军。引用類型的類則不一樣。把一個引用類型的實例賦給一個常量后,依然可以修改該實例的可變屬性奕锌。
必須將延時加載屬性聲明成變量(使用 var 關(guān)鍵字)著觉,因為屬性的初始值可能在實例構(gòu)造完成之后才會得到。而常量屬性在構(gòu)造過程完成之前必須要有初始值惊暴,因此無法聲明成延時加載饼丘。
如果一個被標記為 lazy 的屬性在沒有初始化時就同時被多個線程訪問,則無法保證該屬性只會被初始化一次辽话。
計算屬性不直接存儲值肄鸽,而是提供一個 getter 和一個可選的 setter,來間接獲取和設(shè)置其他屬性或變量的值油啤。
必須使用 var 關(guān)鍵字定義計算屬性典徘,包括只讀計算屬性,因為它們的值不是固定的益咬。let 關(guān)鍵字只用來聲明常量屬性逮诲,表示初始化后再也無法修改的值。
可以為除了延時加載存儲屬性之外的其他存儲屬性添加屬性觀察器幽告,也可以在子類中通過重寫屬性的方式為繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器梅鹦。
在父類初始化方法調(diào)用之后,在子類構(gòu)造器中給父類的屬性賦值時冗锁,會調(diào)用父類屬性的 willSet 和 didSet 觀察器齐唆。而在父類初始化方法調(diào)用之前,給子類的屬性賦值時不會調(diào)用子類屬性的觀察器蒿讥。
帶有觀察器的屬性通過 in-out 方式傳入?yún)?shù)蝶念,willSet 和 didSet 也會調(diào)用抛腕。這是因為 in-out 參數(shù)采用了拷入拷出內(nèi)存模式:即在函數(shù)內(nèi)部使用的是參數(shù)的 copy芋绸,函數(shù)結(jié)束后,又對參數(shù)重新賦值担敌。
全局的常量或變量都是延遲計算的摔敛,跟延時加載存儲屬性相似,不同的地方在于全封,全局的常量或變量不需要標記 lazy 修飾符马昙。
跟實例的存儲型屬性不同,必須給存儲型類型屬性指定默認值刹悴,因為類型本身沒有構(gòu)造器行楞,也就無法在初始化過程中使用構(gòu)造器給類型屬性賦值。存儲型類型屬性是延遲初始化的土匀,它們只有在第一次被訪問的時候才會被初始化子房。即使它們被多個線程同時訪問,系統(tǒng)也保證只會對其進行一次初始化,并且不需要對其使用 lazy 修飾符证杭。
方法
在 Objective-C 中田度,類是唯一能定義方法的類型。但在 Swift 中解愤,不僅能選擇是否要定義一個類/結(jié)構(gòu)體/枚舉镇饺,還能靈活地在創(chuàng)建的類型(類/結(jié)構(gòu)體/枚舉)上定義方法。
實例方法能夠隱式訪問它所屬類型的所有的其他實例方法和屬性送讲。實例方法只能被它所屬的類的某個特定實例調(diào)用奸笤。實例方法不能脫離于現(xiàn)存的實例而被調(diào)用。
結(jié)構(gòu)體和枚舉是值類型哼鬓。默認情況下揭保,值類型的屬性不能在它的實例方法中被修改。如果確實需要在某個特定的方法中修改結(jié)構(gòu)體或者枚舉的屬性魄宏,可以為這個方法選擇可變(mutating)行為秸侣,然后就可以從其方法內(nèi)部改變它的屬性;并且這個方法做的任何改變都會在方法執(zhí)行結(jié)束時寫回到原始結(jié)構(gòu)中宠互。方法還可以給它隱含的 self 屬性賦予一個全新的實例味榛,這個新實例在方法結(jié)束時會替換現(xiàn)存實例。
在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 static予跌,來指定類型方法搏色。類還可以用關(guān)鍵字 class 來指定,從而允許子類重寫父類該方法的實現(xiàn)券册。
在 Objective-C 中频轿,只能為 Objective-C 的類類型(classes)定義類型方法(type-level methods)。在 Swift 中烁焙,可以為所有的類航邢、結(jié)構(gòu)體和枚舉定義類型方法。每一個類型方法都被它所支持的類型顯式包含骄蝇。
下標
下標可以定義在類膳殷、結(jié)構(gòu)體和枚舉中,是訪問集合九火、列表或序列中元素的快捷方式。
下標可以接受任意數(shù)量的入?yún)⒉砑ぃ⑶疫@些入?yún)⒖梢允侨我忸愋腿枘洹O聵说姆祷刂狄部梢允侨我忸愋汀O聵丝梢允褂每勺儏?shù)掀鹅,但是不能使用 in-out 參數(shù)以及不能提供默認參數(shù)散休。
一個類或結(jié)構(gòu)體可以根據(jù)自身需要提供多個下標實現(xiàn),使用下標時將通過入?yún)⒌臄?shù)量和類型進行區(qū)分乐尊,自動匹配合適的下標戚丸。它通常被稱為下標的重載。
繼承
Swift 中的類并不是從一個通用的基類繼承而來的扔嵌。如果不為自己定義的類指定一個超類的話限府,這個類就會自動成為基類。
可以重寫繼承來的實例屬性或類型屬性痢缎,提供自己定制的 getter 和 setter胁勺,或添加屬性觀察器,使重寫的屬性可以觀察到底層的屬性值什么時候發(fā)生改變独旷。
可以提供定制的 getter(或 setter)來重寫任何一個繼承來的屬性署穗,無論這個屬性是存儲型還是計算型屬性。子類并不知道繼承來的屬性是存儲型的還是計算型的嵌洼,它只知道繼承來的屬性會有一個名字和類型案疲。在重寫一個屬性時,必須將它的名字和類型都寫出來麻养。這樣才能使編譯器去檢查重寫的屬性是與超類中同名同類型的屬性相匹配的褐啡。
可以將一個繼承來的只讀屬性重寫為一個讀寫屬性,只需要在重寫版本的屬性里提供 getter 和 setter 即可鳖昌。但是不可以將一個繼承來的讀寫屬性重寫為一個只讀屬性备畦。
如果在重寫屬性中提供了 setter,那么也一定要提供 getter许昨。如果不想在重寫版本中的 getter 里修改繼承來的屬性值懂盐,可以直接通過 super.someProperty 來返回繼承來的值。
不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器车要。這些屬性的值是不可以被設(shè)置的允粤,所以,為它們提供 willSet 或 didSet 實現(xiàn)也是不恰當(dāng)翼岁。不可以同時提供重寫的 setter 和重寫的屬性觀察器。如果想觀察屬性值的變化司光,并且已經(jīng)為那個屬性提供了定制的 setter琅坡,那么在 setter 中就可以觀察到任何值變化了。
通過把方法残家,屬性或下標標記為 final 來防止它們被重寫榆俺,只需要在聲明關(guān)鍵字前加上 final 修飾符即可。
通過在關(guān)鍵字 class 前添加 final 修飾符(final class)來將整個類標記為 final 。這樣的類是不可被繼承的茴晋,試圖繼承這樣的類會導(dǎo)致編譯報錯陪捷。
構(gòu)造過程
在新實例使用前有個過程是必須的,它包括設(shè)置實例中每個存儲屬性的初始值和執(zhí)行其他必須的設(shè)置或構(gòu)造過程诺擅。
與 Objective-C 中的構(gòu)造器不同市袖,Swift 的構(gòu)造器沒有返回值。它們的主要任務(wù)是保證某種類型的新實例在第一次使用前完成正確的初始化烁涌。
當(dāng)為存儲型屬性分配默認值或者在構(gòu)造器中為設(shè)置初始值時苍碟,它們的值是被直接設(shè)置的,不會觸發(fā)任何屬性觀察者撮执。
如果一個屬性總是使用相同的初始值微峰,那么為其設(shè)置一個默認值比每次都在構(gòu)造器中賦值要好。兩種方法的最終結(jié)果是一樣的抒钱,只不過使用默認值讓屬性的初始化和聲明結(jié)合得更緊密蜓肆。它能讓構(gòu)造器更簡潔、更清晰谋币,且能通過默認值自動推導(dǎo)出屬性的類型症杏;同時,它也能充分利用默認構(gòu)造器瑞信、構(gòu)造器繼承等特性厉颤。
自定義的類型有一個邏輯上允許值為空的存儲型屬性——無論是因為它無法在初始化時賦值,還是因為它在之后某個時機可以賦值為空——都需要將它聲明為 可選類型凡简”朴眩可選類型的屬性將自動初始化為 nil,表示這個屬性是特意在構(gòu)造過程設(shè)置為空秤涩。
可以在構(gòu)造過程中的任意時間點給常量屬性賦值帜乞,一旦常量屬性被賦值,它將永遠不可更改筐眷。
對于類的實例來說黎烈,常量屬性(沒有初始化)只能在定義它的類的構(gòu)造過程中修改,不能在子類中修改匀谣。
如果結(jié)構(gòu)體或類為所有屬性提供了默認值照棋,又沒有提供任何自定義的構(gòu)造器,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器武翎。這個默認構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為它們默認值的實例烈炭。調(diào)用一個逐一成員構(gòu)造器(memberwise initializer)時,可以省略任何一個有默認值的屬性宝恶。
如果結(jié)構(gòu)體如果沒有定義任何自定義構(gòu)造器符隙,它們將自動獲得一個逐一成員構(gòu)造器(memberwise initializer)趴捅。不像默認構(gòu)造器,即使存儲型屬性沒有默認值霹疫,結(jié)構(gòu)體也能會獲得逐一成員構(gòu)造器拱绑。
構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理丽蝎,它能避免多個構(gòu)造器間的代碼重復(fù)猎拨。
如果為某個值類型定義了一個自定義的構(gòu)造器,將無法訪問到默認構(gòu)造器(如果是結(jié)構(gòu)體征峦,還將無法訪問逐一成員構(gòu)造器)迟几。這種限制避免了在一個更復(fù)雜的構(gòu)造器中做了額外的重要設(shè)置,但有人不小心使用自動生成的構(gòu)造器而導(dǎo)致錯誤的情況栏笆。假如希望默認構(gòu)造器类腮、逐一成員構(gòu)造器以及自己的自定義構(gòu)造器都能用來創(chuàng)建實例,可以將自定義的構(gòu)造器寫到擴展(extension)中蛉加,而不是寫在值類型的原始定義中蚜枢。
類的繼承和構(gòu)造過程
類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值。
指定構(gòu)造器將初始化類中提供的所有屬性针饥,并調(diào)用合適的父類構(gòu)造器讓構(gòu)造過程沿著父類鏈繼續(xù)往上進行厂抽。每一個類都必須至少擁有一個指定構(gòu)造器。在某些情況下丁眼,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個條件筷凤。
便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器苞七。只在必要的時候為類提供便利構(gòu)造器藐守,便利構(gòu)造器也采用相同樣式的寫法,但需要在 init 關(guān)鍵字之前放置 convenience 關(guān)鍵字蹂风,并使用空格將它們倆分開卢厂。
Swift 構(gòu)造器之間的代理調(diào)用遵循以下三條規(guī)則:
- 指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。
- 便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器惠啄。
- 便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器慎恒。
一個更方便記憶的方法是:指定構(gòu)造器必須總是向上代理,便利構(gòu)造器必須總是橫向代理撵渡。
Swift 中類的構(gòu)造過程包含兩個階段融柬。第一個階段,類中的每個存儲型屬性賦一個初始值姥闭。當(dāng)每個存儲型屬性的初始值被賦值后丹鸿,第二階段開始,它給每個類一次機會棚品,在新實例準備使用之前進一步自定義它們的存儲型屬性靠欢。
安全檢查1:指定構(gòu)造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器铜跑。
安全檢查2:指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類構(gòu)造器门怪。如果沒這么做(編譯報錯),指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋锅纺。
安全檢查3:便利構(gòu)造器必須為任意屬性(包括所有同類中定義的)賦新值之前代理調(diào)用其它構(gòu)造器掷空。如果沒這么做(編譯報錯),便利構(gòu)造器賦予的新值將被該類的指定構(gòu)造器所覆蓋囤锉。
安全檢查4:構(gòu)造器在第一階段構(gòu)造完成之前坦弟,不能調(diào)用任何實例方法,不能讀取任何實例屬性的值官地,不能引用 self 作為一個值酿傍。
階段1
- 類的某個指定構(gòu)造器或便利構(gòu)造器被調(diào)用。
- 完成類的新實例內(nèi)存的分配驱入,但此時內(nèi)存還沒有被初始化赤炒。
- 指定構(gòu)造器確保其所在類引入的所有存儲型屬性都已賦初值。存儲型屬性所屬的內(nèi)存完成初始化亏较。
- 指定構(gòu)造器切換到父類的構(gòu)造器莺褒,對其存儲屬性完成相同的任務(wù)。
- 這個過程沿著類的繼承鏈一直往上執(zhí)行雪情,直到到達繼承鏈的最頂部遵岩。
- 當(dāng)?shù)竭_了繼承鏈最頂部,而且繼承鏈的最后一個類已確保所有的存儲型屬性都已經(jīng)賦值巡通,這個實例的內(nèi)存被認為已經(jīng)完全初始化尘执。此時階段 1 完成。
階段2
- 從繼承鏈頂部往下扁达,繼承鏈中每個類的指定構(gòu)造器都有機會進一步自定義實例正卧。構(gòu)造器此時可以訪問 self、修改它的屬性并調(diào)用實例方法等等跪解。
- 最終炉旷,繼承鏈中任意的便利構(gòu)造器有機會自定義實例和使用 self。
Swift 中的子類默認情況下不會繼承父類的構(gòu)造器叉讥,Swift 的這種機制可以防止一個父類的簡單構(gòu)造器被一個更精細的子類繼承窘行,而在用來創(chuàng)建子類的新實例時沒有完全或錯誤地被初始化。
可以在子類中提供這些構(gòu)造器的自定義實現(xiàn)图仓,當(dāng)在編寫一個和父類中指定構(gòu)造器相匹配的子類構(gòu)造器時罐盔,實際上是在重寫父類的這個指定構(gòu)造器。因此救崔,必須在定義子類構(gòu)造器時帶上 override 修飾符惶看。即使重寫的是系統(tǒng)自動提供的默認構(gòu)造器捏顺,也需要帶上 override 修飾符。相反纬黎,如果編寫一個和父類便利構(gòu)造器相匹配的子類構(gòu)造器颓鲜,由于子類不能直接調(diào)用父類的便利構(gòu)造器沮明,因此椎麦,嚴格意義上來講仙蛉,子類并未對父類構(gòu)造器提供重寫。最后的結(jié)果就是冠息,在子類中“重寫”一個父類便利構(gòu)造器時挪凑,不需要加 override 修飾符。
構(gòu)造器自動繼承規(guī)則:
- 如果子類沒有定義任何指定構(gòu)造器逛艰,它將自動繼承父類所有的指定構(gòu)造器躏碳。
- 如果子類提供了所有父類指定構(gòu)造器的實現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實現(xiàn)——它將自動繼承父類所有的便利構(gòu)造器瓮孙。
子類可以將父類的指定構(gòu)造器實現(xiàn)為便利構(gòu)造器唐断。
如果代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個構(gòu)造過程將立即終止杭抠,接下來的任何構(gòu)造代碼不會再被執(zhí)行脸甘。
當(dāng)用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時,向上代理到父類的可失敗構(gòu)造器的唯一方式是對父類的可失敗構(gòu)造器的返回值進行強制解包偏灿。
可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器丹诀,但反過來卻不行。
在 init 關(guān)鍵字后添加問號的方式(init?)來定義一個可失敗構(gòu)造器翁垂,但也可以通過在 init 后面添加感嘆號的方式來定義一個可失敗構(gòu)造器(init!)铆遭,該可失敗構(gòu)造器將會構(gòu)建一個對應(yīng)類型的隱式解包可選類型的對象。
在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實現(xiàn)該構(gòu)造器沿猜。在子類重寫父類的必要構(gòu)造器時枚荣,必須在子類的構(gòu)造器前也添加 required 修飾符,表明該構(gòu)造器要求也應(yīng)用于繼承鏈后面的子類啼肩。在重寫父類中必要的指定構(gòu)造器時橄妆,不需要添加 override 修飾符。如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的要求祈坠,則無須在子類中顯式提供必要構(gòu)造器的實現(xiàn)害碾。
如果某個存儲型屬性的默認值需要一些自定義或設(shè)置,可以使用閉包或全局函數(shù)為其提供定制的默認值赦拘。每當(dāng)某個屬性所在類型的新實例被構(gòu)造時慌随,對應(yīng)的閉包或函數(shù)會被調(diào)用,而它們的返回值會當(dāng)做默認值賦值給這個屬性。
使用閉包來初始化屬性阁猜,請記住在閉包執(zhí)行時丸逸,實例的其它部分都還沒有初始化。這意味著不能在閉包里訪問其它屬性蹦漠,即使這些屬性有默認值椭员。同樣车海,也不能使用隱式的 self 屬性笛园,或者調(diào)用任何實例方法。
析構(gòu)過程
析構(gòu)器是在實例釋放發(fā)生前被自動調(diào)用的侍芝。不能主動調(diào)用析構(gòu)器研铆。子類繼承了父類的析構(gòu)器,并且在子類析構(gòu)器實現(xiàn)的最后州叠,父類的析構(gòu)器會被自動調(diào)用棵红。即使子類沒有提供自己的析構(gòu)器,父類的析構(gòu)器也同樣會被調(diào)用咧栗。
可選鏈
略
錯誤處理
Swift 中有 4 種處理錯誤的方式:
- 可以把函數(shù)拋出的錯誤傳遞給調(diào)用此函數(shù)的代碼
- 用 do-catch 語句處理錯誤
- 將錯誤作為可選類型處理
- 或者斷言此錯誤根本不會發(fā)生逆甜。
只有 throwing 函數(shù)可以傳遞錯誤。任何在某個非 throwing 函數(shù)內(nèi)部拋出的錯誤只能在函數(shù)內(nèi)部處理致板。
使用 try? 通過將錯誤轉(zhuǎn)換成一個可選值來處理錯誤交煞。如果是在計算 try? 表達式時拋出錯誤,該表達式的結(jié)果就為 nil斟或。
在表達式前面寫 try! 來禁用錯誤傳遞素征,這會把調(diào)用包裝在一個不會有錯誤拋出的運行時斷言中。如果真的拋出了錯誤萝挤,會得到一個運行時錯誤御毅。
使用 defer 語句在即將離開當(dāng)前代碼塊時執(zhí)行一系列語句。該語句能執(zhí)行一些必要的清理工作怜珍,不管是以何種方式離開當(dāng)前代碼塊的——無論是由于拋出錯誤而離開端蛆,或是由于諸如 return、break 的語句酥泛。
defer 語句將代碼的執(zhí)行延遲到當(dāng)前的作用域退出之前今豆。延遲執(zhí)行的語句不能包含任何控制轉(zhuǎn)移語句,例如 break揭璃、return 語句晚凿,或是拋出一個錯誤。延遲執(zhí)行的操作會按照它們聲明的順序從后往前執(zhí)行——也就是說瘦馍,第一條 defer 語句中的代碼最后才執(zhí)行歼秽,第二條 defer 語句中的代碼倒數(shù)第二個執(zhí)行,以此類推情组。最后一條語句會第一個執(zhí)行燥筷。
類型轉(zhuǎn)換
Swift 為不確定類型提供了兩種特殊的類型別名:
- Any 可以表示任何類型箩祥,包括函數(shù)類型。
- AnyObject 可以表示任何類類型的實例肆氓。
Any 類型可以表示所有類型的值袍祖,包括可選類型。Swift 會在用 Any 類型來表示一個可選值的時候谢揪,給一個警告蕉陋。如果確實想使用 Any 類型來承載可選值,可以使用 as 操作符顯式轉(zhuǎn)換為 Any
擴展
擴展和 Objective-C 的分類很相似拨扶。(與 Objective-C 分類不同的是凳鬓,Swift 擴展是沒有名字的。)
Swift 中的擴展可以:
- 添加計算型實例屬性和計算型類屬性
- 定義實例方法和類方法
- 提供新的構(gòu)造器
- 定義下標
- 定義和使用新的嵌套類型
- 使已經(jīng)存在的類型遵循(conform)一個協(xié)議
擴展可以給一個類型添加新的功能患民,但是不能重寫已經(jīng)存在的功能缩举。
擴展可以給一個類添加新的便利構(gòu)造器,但是它們不能給類添加新的指定構(gòu)造器或者析構(gòu)器匹颤。指定構(gòu)造器和析構(gòu)器必須始終由類的原始實現(xiàn)提供仅孩。
如果使用擴展給一個值類型添加構(gòu)造器只是用于給所有的存儲屬性提供默認值,并且沒有定義任何自定義構(gòu)造器印蓖,那么可以在該值類型擴展的構(gòu)造器中使用默認構(gòu)造器和成員構(gòu)造器辽慕。
協(xié)議
協(xié)議 規(guī)定了用來實現(xiàn)某一特定任務(wù)或者功能的方法、屬性另伍,以及其他需要的東西鼻百。類、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議摆尝,并為協(xié)議定義的這些要求提供具體實現(xiàn)温艇。某個類型能夠滿足某個協(xié)議的要求,就可以說該類型遵循這個協(xié)議勺爱。
除了遵循協(xié)議的類型必須實現(xiàn)的要求外,還可以對協(xié)議進行擴展讯检,通過擴展來實現(xiàn)一部分要求或者實現(xiàn)一些附加功能琐鲁,這樣遵循協(xié)議的類型就能夠使用這些功能。
協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實例屬性或類型屬性人灼。協(xié)議不指定屬性是存儲屬性還是計算屬性围段,它只指定屬性的名稱和類型。
協(xié)議還指定屬性是可讀的還是可讀可寫的投放。如果協(xié)議要求屬性是可讀可寫的奈泪,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協(xié)議只要求屬性是可讀的,那么該屬性不僅可以是可讀的涝桅,如果代碼需要的話拜姿,還可以是可寫的。
協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)某些指定的實例方法或類方法冯遂∪锓剩可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同蛤肌。但是壁却,不支持為協(xié)議中的方法提供默認參數(shù)。
實現(xiàn)協(xié)議中的 mutating 方法時寻定,若是類類型儒洛,則不用寫 mutating 關(guān)鍵字。而對于結(jié)構(gòu)體和枚舉狼速,則必須寫 mutating 關(guān)鍵字。
在遵循協(xié)議的類中實現(xiàn)構(gòu)造器卦停,無論是作為指定構(gòu)造器向胡,還是作為便利構(gòu)造器。無論哪種情況惊完,都必須為構(gòu)造器實現(xiàn)標上 required 修飾符僵芹。
如果類已經(jīng)被標記為 final,那么不需要在協(xié)議構(gòu)造器的實現(xiàn)中使用 required 修飾符小槐,因為 final 類不能有子類拇派。
如果一個子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個協(xié)議的要求凿跳,那么該構(gòu)造器的實現(xiàn)需要同時標注 required 和 override 修飾符件豌。
協(xié)議可以像其他普通類型一樣使用,使用場景如下:
- 作為函數(shù)控嗜、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量茧彤、變量或?qū)傩缘念愋?/li>
- 作為數(shù)組、字典或其他容器中的元素類型
即使?jié)M足了協(xié)議的所有要求疆栏,類型也不會自動遵循協(xié)議曾掂,必須顯式地遵循協(xié)議。
通過添加 AnyObject 關(guān)鍵字到協(xié)議的繼承列表壁顶,就可以限制協(xié)議只能被類類型準守(以及非結(jié)構(gòu)體或者非枚舉的類型)珠洗。
協(xié)議組合使用 SomeProtocol & AnotherProtocol 的形式∪糇ǎ可以列舉任意數(shù)量的協(xié)議许蓖,用和符號(&)分開。除了協(xié)議列表,協(xié)議組合也能包含類類型蛔糯,這允許標明一個需要的父類拯腮。協(xié)議組合行為就和定義的臨時局部協(xié)議一樣擁有構(gòu)成中所有協(xié)議的需求。協(xié)議組合不定義任何新的協(xié)議類型蚁飒。
使用 類型轉(zhuǎn)換 中描述的 is 和 as 操作符來檢查協(xié)議一致性动壤,即是否遵循某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型淮逻。檢查和轉(zhuǎn)換協(xié)議的語法與檢查和轉(zhuǎn)換類型是完全一樣的:
- is 用來檢查實例是否遵循某個協(xié)議琼懊,若遵循則返回 true,否則返回 false爬早;
- as? 返回一個可選值哼丈,當(dāng)實例遵循某個協(xié)議時,返回類型為協(xié)議類型的可選值筛严,否則返回 nil醉旦;
- as! 將實例強制向下轉(zhuǎn)換到某個協(xié)議類型,如果強轉(zhuǎn)失敗桨啃,將觸發(fā)運行時錯誤车胡。
在協(xié)議中使用 optional 關(guān)鍵字作為前綴來定義可選要求≌振可選要求用在需要和 Objective-C 打交道的代碼中匈棘。協(xié)議和可選要求都必須帶上 @objc 屬性。標記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 @objc 類遵循析命,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議主卫。
通過擴展來為遵循協(xié)議的類型提供屬性、方法以及下標的實現(xiàn)鹃愤。通過這種方式簇搅,可以基于協(xié)議本身來實現(xiàn)這些功能,而無需在每個遵循協(xié)議的類型中都重復(fù)同樣的實現(xiàn)昼浦,也無需使用全局函數(shù)馍资。通過協(xié)議擴展,所有遵循協(xié)議的類型关噪,都能自動獲得這個擴展所增加的方法實現(xiàn)鸟蟹。
協(xié)議擴展可以為遵循協(xié)議的類型增加實現(xiàn),但不能聲明該協(xié)議繼承自另一個協(xié)議使兔。協(xié)議的繼承只能在協(xié)議聲明處進行指定建钥。
通過協(xié)議擴展來為協(xié)議要求的方法、計算屬性提供默認的實現(xiàn)虐沥。如果遵循協(xié)議的類型為這些要求提供了自己的實現(xiàn)熊经,那么這些自定義實現(xiàn)將會替代擴展中的默認實現(xiàn)被使用泽艘。
通過協(xié)議擴展為協(xié)議要求提供的默認實現(xiàn)和可選的協(xié)議要求不同。雖然在這兩種情況下镐依,遵循協(xié)議的類型都無需自己實現(xiàn)這些要求匹涮,但是通過擴展提供的默認實現(xiàn)可以直接調(diào)用,而無需使用可選鏈式調(diào)用槐壳。
可以指定一些限制條件然低,只有遵循協(xié)議的類型滿足這些限制條件時,才能獲得協(xié)議擴展提供的默認實現(xiàn)务唐。這些限制條件寫在協(xié)議名之后雳攘,使用 where 子句來描述。
泛型
泛型代碼能根據(jù)自定義的需求枫笛,編寫出適用于任意類型的吨灭、靈活可復(fù)用的函數(shù)及類型。避免編寫重復(fù)的代碼刑巧,而是用一種清晰抽象的方式來表達代碼的意圖喧兄。
始終使用大寫字母開頭的駝峰命名法(例如 T 和 MyTypeParameter)來為類型參數(shù)命名,以表明它們是占位類型海诲,而不是一個值繁莹。
當(dāng)對泛型類型進行擴展時,并不需要提供類型參數(shù)列表作為定義的一部分特幔。原始類型定義中聲明的類型參數(shù)列表在擴展中可以直接使用,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用闸昨。
當(dāng)自定義泛型類型時蚯斯,可以定義自己的類型約束,這些約束將提供更為強大的泛型編程能力饵较。像 可哈希(hashable) 這種抽象概念根據(jù)它們的概念特征來描述類型拍嵌,而不是它們的具體類型。
定義一個協(xié)議時循诉,聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分將會非常有用横辆。關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,其代表的實際類型在協(xié)議被遵循時才會被指定茄猫。關(guān)聯(lián)類型通過 associatedtype 關(guān)鍵字來指定狈蚤。
可以利用擴展讓一個已存在的類型遵循一個協(xié)議,這包括使用了關(guān)聯(lián)類型協(xié)議划纽。
可以在協(xié)議里給關(guān)聯(lián)類型添加約束來要求遵循的類型滿足約束脆侮。
類型約束 能夠為泛型函數(shù)、下標勇劣、類型的類型參數(shù)定義一些強制要求靖避。
對關(guān)聯(lián)類型添加約束通常是非常有用的潭枣。可以通過定義一個泛型 where 子句來實現(xiàn)幻捏。通過泛型 where 子句讓關(guān)聯(lián)類型遵從某個特定的協(xié)議盆犁,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同〈劬牛可以通過將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來定義 where 子句谐岁,where 子句后跟一個或者多個針對關(guān)聯(lián)類型的約束,以及一個或多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系瓮下『舱。可以在函數(shù)體或者類型的大括號之前添加 where 子句。
可以使用泛型 where 子句作為擴展的一部分讽坏。
可以在關(guān)聯(lián)類型后面加上具有泛型 where 的字句锭魔。
下標可以是泛型,它們能夠包含泛型 where 子句路呜∶耘酰可以在 subscript 后用尖括號來寫占位符類型,還可以在下標代碼塊花括號前寫 where 子句胀葱。
不透明類型
具有不透明返回類型的函數(shù)或方法會隱藏返回值的類型信息漠秋。函數(shù)不再提供具體的類型作為返回類型,而是根據(jù)它支持的協(xié)議來描述返回值抵屿。在處理模塊和調(diào)用代碼之間的關(guān)系時庆锦,隱藏類型信息非常有用,因為返回的底層數(shù)據(jù)類型仍然可以保持私有轧葛。而且不同于返回協(xié)議類型搂抒,不透明類型能保證類型一致性 —— 編譯器能獲取到類型信息,同時模塊使用者卻不能獲取到尿扯。
可以認為不透明類型和泛型相反求晶。泛型允許調(diào)用一個方法時,為這個方法的形參和返回值指定一個與實現(xiàn)無關(guān)的類型衷笋。不透明類型允許函數(shù)實現(xiàn)時芳杏,選擇一個與調(diào)用代碼無關(guān)的返回類型。
函數(shù)中有多個地方返回了不透明類型辟宗,那么所有可能的返回值都必須是同一類型爵赵。即使對于泛型函數(shù),不透明返回類型可以使用泛型參數(shù)慢蜓,但仍需保證返回類型唯一亚再。
返回類型始終唯一的要求,并不會影響在返回的不透明類型中使用泛型晨抡,只是在返回的底層類型中使用了泛型參數(shù)氛悬。
不透明類型作為函數(shù)返回值则剃,看起來和返回協(xié)議類型非常相似,但這兩者有一個主要區(qū)別如捅,就在于是否需要保證類型一致性棍现。一個不透明類型只能對應(yīng)一個具體的類型,即便函數(shù)調(diào)用者并不能知道是哪一種類型镜遣;協(xié)議類型可以同時對應(yīng)多個類型己肮,只要它們都遵循同一協(xié)議”兀總的來說谎僻,協(xié)議類型更具靈活性,底層類型可以存儲更多樣的值寓辱,而不透明類型對這些底層類型有更強的限定艘绍。
將協(xié)議類型作為函數(shù)的返回類型能更加靈活,函數(shù)只要返回遵循協(xié)議的類型即可秫筏。然而诱鞠,更具靈活性導(dǎo)致犧牲了對返回值執(zhí)行某些操作的能力。為什么不能使用 == 運算符 —— 它依賴于具體的類型信息这敬,而這正是使用協(xié)議類型所無法提供的航夺。
相比之下,不透明類型則保留了底層類型的唯一性崔涂。Swift 能夠推斷出關(guān)聯(lián)類型阳掐,這個特點使得作為函數(shù)返回值,不透明類型比協(xié)議類型有更大的使用場景冷蚂。
不能將有一個關(guān)聯(lián)類型的協(xié)議作為方法的返回類型锚烦,也不能將它用于對泛型返回類型的約束,因為函數(shù)體之外并沒有暴露足夠多的信息來推斷泛型類型帝雇。
自動引用計數(shù)
引用計數(shù)僅僅應(yīng)用于類的實例。結(jié)構(gòu)體和枚舉類型是值類型蛉拙,不是引用類型尸闸,也不是通過引用的方式存儲和傳遞。
每當(dāng)創(chuàng)建一個新的類實例時孕锄,ARC 會分配一塊內(nèi)存來儲存該實例的信息吮廉。內(nèi)存中會包含實例的類型信息,以及這個實例所關(guān)聯(lián)的任何存儲屬性的值畸肆。當(dāng)實例不再被使用時宦芦,ARC 釋放實例所占用的內(nèi)存,并讓釋放的內(nèi)存能挪作他用轴脐。這確保了不再被使用的實例调卑,不會一直占用內(nèi)存空間抡砂。
當(dāng) ARC 回收并釋放了正在被使用中的實例后,該實例的屬性和方法將不能再被訪問和調(diào)用恬涧。實際上注益,如果試圖訪問這個實例,應(yīng)用程序很可能會崩潰溯捆。無論將實例賦值給屬性丑搔、常量或變量,它們都會創(chuàng)建此實例的強引用提揍。之所以稱之為“強”引用啤月,是因為它會將實例牢牢地保持住,只要強引用還在劳跃,實例是不允許被銷毀的谎仲。
弱引用和無主引用允許循環(huán)引用中的一個實例引用另一個實例而不保持強引用。這樣實例能夠互相引用而不產(chǎn)生循環(huán)強引用售碳。
當(dāng)其他的實例有更短的生命周期時强重,使用弱引用,也就是說贸人,當(dāng)其他實例析構(gòu)在先時间景。相比之下,當(dāng)其他實例有相同的或者更長生命周期時艺智,請使用無主引用倘要。
當(dāng) ARC 設(shè)置弱引用為 nil 時,屬性觀察不會被觸發(fā)十拣。
聲明屬性或者變量時封拧,在前面加上 weak 關(guān)鍵字表明這是一個弱引用。因為弱引用不會保持所引用的實例夭问,即使引用存在泽西,實例也有可能被銷毀。因此缰趋,ARC 會在引用的實例被銷毀后自動將其弱引用賦值為 nil捧杉。并且因為弱引用需要在運行時允許被賦值為 nil,所以它們會被定義為可選類型變量秘血,而不是常量味抖。
聲明屬性或者變量時,在前面加上關(guān)鍵字 unowned 表示這是一個無主引用灰粮。無主引用通常都被期望擁有值仔涩。不過 ARC 無法在實例被銷毀后將無主引用設(shè)為 nil,因為非可選類型的變量不允許被賦值為 nil粘舟。
使用無主引用熔脂,必須確保引用始終指向一個未銷毀的實例佩研。如果試圖在實例被銷毀后,訪問該實例的無主引用锤悄,會觸發(fā)運行時錯誤韧骗。
對于需要禁用運行時的安全檢查的情況(例如,出于性能方面的原因)零聚,Swift 還提供了不安全的無主引用袍暴。與所有不安全的操作一樣,需要負責(zé)檢查代碼以確保其安全性隶症。 可以通過 unowned(unsafe) 來聲明不安全無主引用政模。如果試圖在實例被銷毀后,訪問該實例的不安全無主引用蚂会,程序會嘗試訪問該實例之前所在的內(nèi)存地址淋样,這是一個不安全的操作。
- 兩個屬性的值都允許為 nil胁住,并會潛在的產(chǎn)生循環(huán)強引用趁猴。這種場景最適合用弱引用來解決。
- 一個屬性的值允許為 nil彪见,而另一個屬性的值不允許為 nil儡司,這也可能會產(chǎn)生循環(huán)強引用。這種場景最適合通過無主引用來解決余指。
- 兩個屬性都必須有值捕犬,并且初始化完成后永遠不會為 nil。在這種場景中酵镜,需要一個類使用無主屬性碉碉,而另外一個類使用隱式解包可選值屬性(利用隱式解包可選值默認為nil,從而滿足了類的構(gòu)造器的兩個構(gòu)造階段的要求)淮韭。
使用隱式解包可選值值意味著滿足了類的構(gòu)造器的兩個構(gòu)造階段的要求垢粮。屬性在初始化完成后,能像非可選值一樣使用和存取靠粪,同時還避免了循環(huán)強引用足丢。
閉包和類相似,都是引用類型庇配。
在默認的閉包中可以使用 self男韧,因為只有當(dāng)初始化完成以及 self 確實存在后将硝,才能訪問 lazy 屬性筏餐。
在定義閉包時同時定義捕獲列表作為閉包的一部分粟瞬,通過這種方式可以解決閉包和類實例之間的循環(huán)強引用劳吠。捕獲列表定義了閉包體內(nèi)捕獲一個或者多個引用類型的規(guī)則。跟解決兩個類實例間的循環(huán)強引用一樣飞崖,聲明每個捕獲的引用為弱引用或無主引用慰毅,而不是強引用。應(yīng)當(dāng)根據(jù)代碼關(guān)系來決定使用弱引用還是無主引用嗅虏。捕獲列表中的每一項都由一對元素組成洛姑,一個元素是 weak 或 unowned 關(guān)鍵字,另一個元素是類實例的引用(例如 self)或初始化過的變量(如 delegate = self.delegate)皮服。這些項在方括號中用逗號分開楞艾。
Swift 有如下要求:只要在閉包內(nèi)使用 self 的成員,就要用 self.someProperty 或者 self.someMethod()(而不只是 someProperty 或 someMethod())龄广。這提醒可能會一不小心就捕獲了 self硫眯。
在閉包和捕獲的實例總是互相引用并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為 無主引用择同。相反两入,在被捕獲的引用可能會變?yōu)?nil 時,將閉包內(nèi)的捕獲定義為 弱引用敲才。弱引用總是可選類型裹纳,并且當(dāng)引用的實例被銷毀后,弱引用的值會自動置為 nil紧武。這使可以在閉包體內(nèi)檢查它們是否存在剃氧。如果被捕獲的引用絕對不會變?yōu)?nil,應(yīng)該用無主引用脏里,而不是弱引用她我。
內(nèi)存安全
默認情況下,Swift 會阻止代碼里不安全的行為迫横。Swift 會保證變量在使用之前就完成初始化番舆,在內(nèi)存被回收之后就無法被訪問,并且數(shù)組的索引會做越界檢查矾踱。
Swift 也保證同時訪問同一塊內(nèi)存時不會沖突恨狈,通過約束代碼里對于存儲地址的寫操作,去獲取那一塊內(nèi)存的訪問獨占權(quán)呛讲。而如果代碼確實存在沖突禾怠,那在編譯時或者運行時就會得到錯誤。
內(nèi)存訪問的沖突會發(fā)生在代碼嘗試同時訪問同一個存儲地址的時侯贝搁。同一個存儲地址的多個訪問同時發(fā)生會造成不可預(yù)計或不一致的行為吗氏。在 Swift 里,有很多修改值的行為都會持續(xù)好幾行代碼雷逆,在修改值的過程中進行訪問是有可能發(fā)生的弦讽。
內(nèi)存訪問沖突時,要考慮內(nèi)存訪問上下文中的這三個性質(zhì):訪問是讀還是寫,訪問的時長往产,以及被訪問的存儲地址被碗。特別是,沖突會發(fā)生在有兩個訪問符合下列的情況:
- 至少有一個是寫訪問
- 它們訪問的是同一個存儲地址
- 它們的訪問在時間線上部分重疊
如果一個訪問不可能在其訪問期間被其它代碼訪問仿村,那么就是一個瞬時訪問锐朴。正常來說,兩個瞬時訪問是不可能同時發(fā)生的蔼囊。大多數(shù)內(nèi)存訪問都是瞬時的焚志。
有幾種被稱為長期訪問的內(nèi)存訪問方式,會在別的代碼執(zhí)行時持續(xù)進行压真。瞬時訪問和長期訪問的區(qū)別在于別的代碼有沒有可能在訪問期間同時訪問娩嚼,也就是在時間線上的重疊。一個長期訪問可以被別的長期訪問或瞬時訪問重疊滴肿。重疊的訪問主要出現(xiàn)在使用 in-out 參數(shù)的函數(shù)和方法或者結(jié)構(gòu)體的 mutating 方法里岳悟。
一個函數(shù)會對它所有的 in-out 參數(shù)進行長期寫訪問。in-out 參數(shù)的寫訪問會在所有非 in-out 參數(shù)處理完之后開始泼差,直到函數(shù)執(zhí)行完畢為止贵少。如果有多個 in-out 參數(shù),則寫訪問開始的順序與參數(shù)的順序一致堆缘。長期訪問的存在會造成一個結(jié)果滔灶,不能在訪問以 in-out 形式傳入后的原變量,即使作用域原則和訪問權(quán)限允許——任何訪問原變量的行為都會造成沖突吼肥。
值類型录平,修改值的任何一部分都是對于整個值的修改,意味著其中一個屬性的讀或?qū)懺L問都需要訪問整一個值缀皱。
即使有些代碼違反了訪問獨占權(quán)的原則斗这,也是內(nèi)存安全的,所以如果編譯器可以保證這種非專屬的訪問是安全的啤斗,那 Swift 就會允許這種行為的代碼運行表箭。特別是當(dāng)遵循下面的原則時,它可以保證結(jié)構(gòu)體屬性的重疊訪問是安全的:
- 訪問的是實例的存儲屬性钮莲,而不是計算屬性或類的屬性
- 結(jié)構(gòu)體是局部變量的值免钻,而非全局變量
- 結(jié)構(gòu)體要么沒有被閉包捕獲,要么只被非逃逸閉包捕獲了
訪問控制
在 Swift 中崔拥,Xcode 的每個 target(例如框架或應(yīng)用程序)都被當(dāng)作獨立的模塊處理极舔。如果為了實現(xiàn)某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨立的框架链瓦,這個框架就是 Swift 中的一個模塊姆怪。當(dāng)它被導(dǎo)入到某個應(yīng)用程序或者其他框架時,框架的內(nèi)容都將屬于這個獨立的模塊。
源文件 就是 Swift 模塊中的源代碼文件(實際上稽揭,源文件屬于一個應(yīng)用程序或框架)。盡管一般會將不同的類型分別定義在不同的源文件中肥卡,但是同一個源文件也可以包含多個類型溪掀、函數(shù)等的定義。
Swift 為代碼中的實體提供了五種不同的訪問級別步鉴。這些訪問級別不僅與源文件中定義的實體相關(guān)揪胃,同時也與源文件所屬的模塊相關(guān):
- open 和 public 級別可以讓實體被同一模塊源文件中的所有實體訪問,在模塊外也可以通過導(dǎo)入該模塊來訪問源文件里的所有實體氛琢。通常情況下喊递,會使用 open 或 public 級別來指定框架的外部接口。open 和 public 的區(qū)別在后面會提到阳似。
- internal 級別讓實體被同一模塊源文件中的任何實體訪問骚勘,但是不能被模塊外的實體訪問。通常情況下撮奏,如果某個接口只在應(yīng)用程序或框架內(nèi)部使用俏讹,就可以將其設(shè)置為 internal 級別。
- fileprivate 限制實體只能在其定義的文件內(nèi)部訪問畜吊。如果功能的部分實現(xiàn)細節(jié)只需要在文件內(nèi)使用時泽疆,可以使用 fileprivate 來將其隱藏。
- private 限制實體只能在其定義的作用域玲献,以及同一文件內(nèi)的 extension 訪問殉疼。如果功能的部分細節(jié)只需要在當(dāng)前作用域內(nèi)使用時,可以使用 private 來將其隱藏捌年。
open 為最高訪問級別(限制最少)瓢娜,private 為最低訪問級別(限制最多)。
open 只能作用于類和類的成員延窜,它和 public 的區(qū)別主要在于 open 限定的類和成員能夠在模塊外能被繼承和重寫恋腕。將類的訪問級別顯示指定為 open 表明已經(jīng)設(shè)計好了類的代碼,并且充分考慮過這個類在其他模塊中用作父類時的影響逆瑞。
Swift 中的訪問級別遵循一個基本原則:實體不能定義在具有更低訪問級別(更嚴格)的實體中荠藤。例如:
- 一個 public 的變量,其類型的訪問級別不能是 internal获高,fileprivate 或是 private哈肖。因為無法保證變量的類型在使用變量的地方也具有訪問權(quán)限。
- 函數(shù)的訪問級別不能高于它的參數(shù)類型和返回類型的訪問級別念秧。因為這樣就會出現(xiàn)函數(shù)可以在任何地方被訪問淤井,但是它的參數(shù)類型和返回類型卻不可以的情況。
不顯式的指定訪問級別,那么將都有一個 internal 的默認訪問級別币狠,(有一些例外情況游两,稍后說明)。因此漩绵,多數(shù)情況下不需要顯示指定實體的訪問級別贱案。
框架的內(nèi)部實現(xiàn)仍然可以使用默認的訪問級別 internal,當(dāng)需要對框架內(nèi)部其它部分隱藏細節(jié)時可以使用 private 或 fileprivate止吐。對于框架的對外 API 部分宝踪,就需要將它們設(shè)置為 open 或 public 了。
一個類型的訪問級別會影響到類型成員(屬性碍扔、方法瘩燥、構(gòu)造器、下標)的默認訪問級別不同。如果將類型指定為 private 或者 fileprivate 級別厉膀,那么該類型的所有成員的默認訪問級別也會變成 private 或者 fileprivate 級別。如果類型指定為 internal 或 public(或者不明確指定訪問級別套鹅,而使用默認的 internal )站蝠,那么該類型的所有成員的默認訪問級別將是 internal。一個 public 類型的所有成員的訪問級別默認為 internal 級別卓鹿,而不是 public 級別菱魔。這樣做的好處是,在定義公共接口的時候吟孙,可以明確地選擇哪些接口是需要公開的澜倦,哪些是內(nèi)部使用的,避免不小心將內(nèi)部使用的接口公開杰妓。
元組的訪問級別將由元組中訪問級別最嚴格的類型來決定藻治。例如一個包含兩種不同類型的元組,其中一個類型為 internal巷挥,另一個類型為 private桩卵,那么這個元組的訪問級別為 private。元組不同于類倍宾、結(jié)構(gòu)體雏节、枚舉、函數(shù)那樣有單獨的定義高职。一個元組的訪問級別由元組中元素的訪問級別來決定的钩乍,不能被顯示指定。
函數(shù)的訪問級別根據(jù)訪問級別最嚴格的參數(shù)類型或返回類型的訪問級別來決定怔锌。但是寥粹,如果這種訪問級別不符合函數(shù)定義所在環(huán)境的默認訪問級別变过,那么就需要明確地指定該函數(shù)的訪問級別。
枚舉成員的訪問級別和該枚舉類型相同涝涤,不能為枚舉成員單獨指定不同的訪問級別媚狰。枚舉定義中的任何原始值或關(guān)聯(lián)值的類型的訪問級別至少不能低于枚舉類型的訪問級別。
嵌套類型的訪問級別和包含它的類型的訪問級別相同阔拳,嵌套類型是 public 的情況除外哈雏。在一個 public 的類型中定義嵌套類型,那么嵌套類型自動擁有 internal 的訪問級別衫生。如果想讓嵌套類型擁有 public 訪問級別,那么必須顯式指定該嵌套類型的訪問級別為 public土浸。
可以繼承同一模塊中的所有有訪問權(quán)限的類罪针,也可以繼承不同模塊中被 open 修飾的類。一個子類的訪問級別不得高于父類的訪問級別黄伊。例如泪酱,父類的訪問級別是 internal,子類的訪問級別就不能是 public还最。在同一模塊中墓阀,可以在符合當(dāng)前訪問級別的條件下重寫任意類成員(方法、屬性拓轻、構(gòu)造器斯撮、下標等)。在不同模塊中扶叉,可以重寫類中被 open 修飾的成員勿锅。可以通過重寫給所繼承類的成員提供更高的訪問級別枣氧。甚至可以在子類中溢十,用子類成員去訪問訪問級別更低的父類成員,只要這一操作在相應(yīng)訪問級別的限制范圍內(nèi)达吞。
常量张弛、變量、屬性不能擁有比它們的類型更高的訪問級別酪劫。例如吞鸭,不能定義一個 public 級別的屬性,但是它的類型卻是 private 級別的契耿。同樣瞒大,下標也不能擁有比索引類型或返回類型更高的訪問級別。
常量搪桂、變量透敌、屬性盯滚、下標的 Getters 和 Setters 的訪問級別和它們所屬類型的訪問級別相同。Setter 的訪問級別可以低于對應(yīng)的 Getter 的訪問級別酗电,這樣就可以控制變量魄藕、屬性或下標的讀寫權(quán)限。在 var 或 subscript 關(guān)鍵字之前撵术,可以通過 fileprivate(set)背率,private(set) 或 internal(set) 為它們的寫入權(quán)限指定更低的訪問級別。這個規(guī)則同時適用于存儲型屬性和計算型屬性嫩与。即使不明確指定存儲型屬性的 Getter 和 Setter寝姿,Swift 也會隱式地為其創(chuàng)建 Getter 和 Setter,用于訪問該屬性的存儲內(nèi)容划滋。使用 fileprivate(set)饵筑,private(set) 和 internal(set) 可以改變 Setter 的訪問級別,這對計算型屬性也同樣適用处坪。
可以在必要時為 Getter 和 Setter 顯式指定訪問級別根资。可以結(jié)合 public 和 private(set) 修飾符把 Getter 的訪問級別設(shè)置為 public同窘,而 Setter 的訪問級別設(shè)置為 private玄帕。
自定義構(gòu)造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是 必要構(gòu)造器想邦,它的訪問級別必須和所屬類型的訪問級別相同裤纹。如同函數(shù)或方法的參數(shù),構(gòu)造器參數(shù)的訪問級別也不能低于構(gòu)造器本身的訪問級別案狠。
默認構(gòu)造器的訪問級別與所屬類型的訪問級別相同服傍,除非類型的訪問級別是 public。如果一個類型被指定為 public 級別骂铁,那么默認構(gòu)造器的訪問級別將為 internal吹零。如果希望一個 public 級別的類型也能在其他模塊中使用這種無參數(shù)的默認構(gòu)造器,只能自己提供一個 public 訪問級別的無參數(shù)構(gòu)造器拉庵。
如果結(jié)構(gòu)體中任意存儲型屬性的訪問級別為 private灿椅,那么該結(jié)構(gòu)體默認的成員逐一構(gòu)造器的訪問級別就是 private。否則钞支,這種構(gòu)造器的訪問級別依然是 internal茫蛹。如同前面提到的默認構(gòu)造器,如果希望一個 public 級別的結(jié)構(gòu)體也能在其他模塊中使用其默認的成員逐一構(gòu)造器烁挟,依然只能自己提供一個 public 訪問級別的成員逐一構(gòu)造器婴洼。
如果想為一個協(xié)議類型明確地指定訪問級別,在聲明協(xié)議時指定即可撼嗓。這將限制該協(xié)議只能在適當(dāng)?shù)脑L問級別范圍內(nèi)被遵循柬采。協(xié)議中的每個方法或?qū)傩远急仨毦哂泻驮搮f(xié)議相同的訪問級別欢唾。不能將協(xié)議中的方法或?qū)傩栽O(shè)置為其他訪問級別。這樣才能確保該協(xié)議的所有方法或?qū)傩詫τ谌我庾裱叨伎捎谩?/p>
如果定義了一個繼承自其他協(xié)議的新協(xié)議粉捻,那么新協(xié)議擁有的訪問級別最高也只能和被繼承協(xié)議的訪問級別相同礁遣。
一個類型可以遵循比它級別更低的協(xié)議。遵循協(xié)議時的上下文級別是類型和協(xié)議中級別最小的那個肩刃。
Extension 的新增成員具有和原始類型成員一致的訪問級別祟霍。可以通過修飾語重新指定 extension 的默認訪問級別(例如盈包,private)沸呐,從而給該 extension 中的所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨成員指定的訪問級別所覆蓋呢燥。
使用 extension 來遵循協(xié)議的話垂谢,就不能顯式地聲明 extension 的訪問級別。extension 每個 protocol 要求的實現(xiàn)都默認使用 protocol 的訪問級別疮茄。
擴展同一文件內(nèi)的類,結(jié)構(gòu)體或者枚舉根暑,extension 里的代碼會表現(xiàn)得跟聲明在原類型里的一模一樣:
- 在類型的聲明里聲明一個私有成員力试,在同一文件的 extension 里訪問。
- 在 extension 里聲明一個私有成員排嫌,在同一文件的另一個 extension 里訪問畸裳。
- 在 extension 里聲明一個私有成員,在同一文件的類型聲明里訪問淳地。
泛型類型或泛型函數(shù)的訪問級別取決于泛型類型或泛型函數(shù)本身的訪問級別怖糊,還需結(jié)合類型參數(shù)的類型約束的訪問級別,根據(jù)這些訪問級別中的最低訪問級別來確定颇象。
定義的任何類型別名都會被當(dāng)作不同的類型伍伤,以便于進行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別遣钳。也適用于為滿足協(xié)議遵循而將類型別名用于關(guān)聯(lián)類型的情況扰魂。
高級運算符
- 按位取反運算符(~)對一個數(shù)值的全部比特位進行取反
- 按位與運算符(&) 對兩個數(shù)的比特位進行合并。它返回一個新的數(shù)蕴茴,只有當(dāng)兩個數(shù)的對應(yīng)位都為 1 的時候劝评,新數(shù)的對應(yīng)位才為 1
- 按位或運算符(|)可以對兩個數(shù)的比特位進行比較。它返回一個新的數(shù)倦淀,只要兩個數(shù)的對應(yīng)位中有任意一個為 1 時蒋畜,新數(shù)的對應(yīng)位就為 1
- 按位異或運算符,或稱“排外的或運算符”(^)撞叽,可以對兩個數(shù)的比特位進行比較姻成。它返回一個新的數(shù)插龄,當(dāng)兩個數(shù)的對應(yīng)位不相同時,新數(shù)的對應(yīng)位就為 1佣渴,并且對應(yīng)位相同時則為 0
- 按位左移運算符(<<) 和 按位右移運算符(>>)可以對一個數(shù)的所有位進行指定位數(shù)的左移和右移
對一個數(shù)進行按位左移或按位右移辫狼,相當(dāng)于對這個數(shù)進行乘以 2 或除以 2 的運算。將一個整數(shù)左移一位辛润,等價于將這個數(shù)乘以 2膨处,同樣地,將一個整數(shù)右移一位砂竖,等價于將這個數(shù)除以 2真椿。
對無符號整數(shù)進行移位的規(guī)則如下:
- 已存在的位按指定的位數(shù)進行左移和右移。
- 任何因移動而超出整型存儲范圍的位都會被丟棄乎澄。
- 用 0 來填充移位后產(chǎn)生的空白位突硝。
有符號整數(shù)使用第 1 個比特位(通常被稱為符號位)來表示這個數(shù)的正負。符號位為 0 代表正數(shù)置济,為 1 代表負數(shù)解恰。
負數(shù)的存儲方式略有不同。它存儲 2 的 n 次方減去其實際值的絕對值浙于,這里的 n 是數(shù)值位的位數(shù)护盈。一個 8 比特位的數(shù)有 7 個比特位是數(shù)值位,所以是 2 的 7 次方羞酗,即 128腐宋。
負數(shù)的表示通常被稱為二進制補碼。用這種方法來表示負數(shù)乍看起來有點奇怪檀轨,但它有幾個優(yōu)點:
- 如果想負數(shù)進行加法運算胸竞,只需要對這兩個數(shù)的全部 8 個比特位執(zhí)行標準的二進制相加(包括符號位),并且將計算結(jié)果中超出 8 位的數(shù)值丟棄参萄。
- 使用二進制補碼可以使負數(shù)的按位左移和右移運算得到跟正數(shù)同樣的效果卫枝,即每向左移一位就將自身的數(shù)值乘以 2,每向右一位就將自身的數(shù)值除以 2讹挎。要達到此目的剃盾,對有符號整數(shù)的右移有一個額外的規(guī)則:當(dāng)對有符號整數(shù)進行按位右移運算時,遵循與無符號整數(shù)相同的規(guī)則淤袜,但是對于移位產(chǎn)生的空白位使用符號位進行填充痒谴,而不是用 0。
Swift 提供的三個溢出運算符來讓系統(tǒng)支持整數(shù)溢出運算铡羡。這些運算符都是以 & 開頭的:溢出加法 &+积蔚、溢出減法 &-、溢出乘法 &*烦周。
運算符的優(yōu)先級使得一些運算符優(yōu)先于其他運算符尽爆;它們會先被執(zhí)行怎顾。結(jié)合性定義了相同優(yōu)先級的運算符是如何結(jié)合的,也就是說漱贱,是與左邊結(jié)合為一組槐雾,還是與右邊結(jié)合為一組》ǎ可以將其理解為“它們是與左邊的表達式結(jié)合的”募强,或者“它們是與右邊的表達式結(jié)合的”。
類和結(jié)構(gòu)體可以為現(xiàn)有的運算符提供自定義的實現(xiàn)崇摄。這通常被稱為運算符重載擎值。運算符函數(shù)必須使用 static。
復(fù)合賦值運算符將賦值運算符(=)與其它運算符進行結(jié)合逐抑。在實現(xiàn)的時候鸠儿,需要把運算符的左參數(shù)設(shè)置成 inout 類型,因為這個參數(shù)的值會在運算符函數(shù)內(nèi)直接被修改厕氨。
不能對默認的賦值運算符(=)進行重載进每。只有復(fù)合賦值運算符可以被重載。同樣地命斧,也無法對三元條件運算符 (a ? b : c) 進行重載品追。
可以使用 Swift 提供的等價運算符默認實現(xiàn)。Swift 為以下數(shù)種準守 Equatable 協(xié)議的自定義類型提供等價運算符的默認實現(xiàn):
- 只擁有存儲屬性冯丙,并且它們?nèi)甲裱?Equatable 協(xié)議的結(jié)構(gòu)體
- 只擁有關(guān)聯(lián)類型,并且它們?nèi)甲裱?Equatable 協(xié)議的枚舉
- 沒有關(guān)聯(lián)類型的枚舉
除了實現(xiàn)標準運算符遭京,在 Swift 中還可以聲明和實現(xiàn)自定義運算符胃惜。使用 operator 關(guān)鍵字在全局作用域內(nèi)進行定義,同時還要指定 prefix哪雕、infix 或者 postfix 修飾符.
每個自定義中綴運算符都屬于某個優(yōu)先級組船殉。優(yōu)先級組指定了這個運算符相對于其他中綴運算符的優(yōu)先級和結(jié)合性。沒有明確放入某個優(yōu)先級組的自定義中綴運算符將會被放到一個默認的優(yōu)先級組內(nèi)斯嚎,其優(yōu)先級高于三元運算符利虫。
當(dāng)定義前綴與后綴運算符的時候,沒有指定優(yōu)先級堡僻。如果對同一個值同時使用前綴與后綴運算符糠惫,則后綴運算符會先參與運算。