Swift 之關鍵字總結

Swift 中有多少關鍵字?

在Swift官方文檔的詞匯結構中, 有非常多的關鍵字, 它們被用于聲明中、語句中、表達式中、類中、模式中, 還有以數字符號#開頭的關鍵字, 以及特定上下文環(huán)境使用的關鍵字。 本文中涉及的代碼可以在這里下載代碼資源。
另外, 在特性中還有一些關鍵字, 是以@開頭的關鍵字。這些所有的關鍵字將在 Swift 之關鍵字總結上篇 和 Swift 之關鍵字總結下篇 兩篇文章中詳細列舉。

本篇主要寫到不帶符號的關鍵字, 如帶#的關鍵字和帶@的特性將在下篇文章中詳細說明。

用在聲明中的關鍵字

associatedtype、class、deinit、enum、extension、func、import、init、inout、internal、let、operator、private、protocol、public、open、fileprivate、static、struct、subscript、typealias 和 var。

用在語句中的關鍵字

break、case、continue、default、defer、do、else、fallthrough、for、guard、if、in、repeat、return、switch、where 和 while 。

用在表達式和類型中的關鍵字

as、catch、dynamicType、false、is、nil , rethrows、super、self、Self、throw、throws、true 和 try 。

特定上下文中被保留的關鍵字

associativity、convenience、dynamic、didSet、final、get、infix、indirect、lazy、left、mutating、none、nonmutating、optional、override、postfix、precedence、prefix、Protocol、required、right、set、Type、unowned、weak 和 willSet 。

起始于數字標記( # )的關鍵字

: #available、#column、#else、#elseif、#endif、#file、#function、#if、#line、#selector 和 #sourceLocation 。

用在模式中的關鍵字

_ 。
以上的關鍵字被預留,不能被用作標識符,除非它們像上一節(jié)標識符中描述的那樣使用反引號( `), 才能使用保留字作為標識符。
有個例外是, 特定上下文中被保留的關鍵字在特定上下文語法之外可以被用于標識符。

以下標記被當作保留符號,不能用于自定義操作符:( 、 ) 、 { 、 } 、 [ 、 ] 、 . 、 ,、 : 、 ; 、 = 、 @ 、 # 、 & (作為前綴操作符)、 -> 、` 、 ? 和 ! (作為后綴操作符)。

關鍵字如何使用?

我想在上面的這些關鍵字中, 大部分的大家應該爛熟于心了。那我就在其中挑選部分特殊的或不常用的來說一說吧。

guard的使用

guard是swift2.0之后出來的,可以加強if語句的可讀性
if語句是這樣的

let a=18
if (a>=18){
    print(已成年)
    if(a<60){
      print(青壯年人)
      if(已結婚){
        print(已婚)
        ...
      }else{
        print(單身)
        ...
      }
    }else{
      print(老人)
  }
}else{
  print(未成年)
}

后面會越來越多,可讀性越來越差
guard 語句是這樣的

let a=18
guard a>=18 else{
  print(未成年)
  return
}
  print(已成年)
guard a<60 else{
  print(老人)
  return
}
print(青壯年人)
guard 已結婚 else{
  print(已婚人士)
  return
}
print(單身)

guard的可讀性明顯就好很多

inout

在函數的入參的類型前添加一個 inout關鍵字可以定義一個輸入輸出形式參數。輸入輸出形式參數有一個能輸入給函數的值,函數能對其進行修改,還能輸出到函數外邊替換原來的值。
你只能把變量作為輸入輸出形式參數的實際參數。你不能用常量或者字面量作為實際參數,因為常量和字面量不能修改。在將變量作為實際參數傳遞給輸入輸出形式參數的時候,直接在它前邊添加一個和符合 ( &) 來明確可以被函數修改。

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var abc = 99
var efg = 88
swapTwoInts(&abc, &efg)

print(abc, efg)
// print result is "88 99\n"

typealias 、協(xié)議組合類型

類型別名可以為已經存在的類型定義了一個新的可選名字。用 typealias 關鍵字定義類型別名。一旦為類型創(chuàng)建了一個別名,你就可以在任何使用原始名字的地方使用這個別名。

typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min

typealias Point = (Int, Int) 
let origin: Point = (0, 0)

關于typealias還有一種很高效的用法與協(xié)議組合類型相關。

協(xié)議組合類型

協(xié)議組合類型允許你指定一個值,該值的類型遵循多個協(xié)議的要求而不必顯式定義一個新的命名型的繼承自每個你想要該類型遵循的協(xié)議的協(xié)議。比如,指定一個協(xié)議組合類型 Protocol A & Protocol B & Protocol C實際上是和定義一個新的繼承自 Protocol A , Protocol B , Protocol C 的協(xié)議 Protocol D 是完全一樣的,但不需要引入一個新名字同理,標明一個協(xié)議組合類型 SuperClass & ProtocolA 與聲明一個新類型 SubClass 繼承自 SuperClass 并遵循 ProtocolA 是一樣的,但不需要引入新名字。
協(xié)議組合列表中的每項元素必須是類名,協(xié)議名或協(xié)議組合類型、協(xié)議、類的類型別名。列表可以最多包含一個類。

當協(xié)議組合類型包含類型別名,就有可能同一個協(xié)議在定義中出現不止一次——重復會被忽略。比如說,下面的 PQR 定義等價于 P & Q & R 。

typealias PQ = P & Q
typealias PQR = PQ & Q & R

associatedtype

定義一個協(xié)議時,有時在協(xié)議定義里聲明一個或多個關聯(lián)類型是很有用的。關聯(lián)類型給協(xié)議中用到的類型一個占位符名稱。直到采納協(xié)議時,才指定用于該關聯(lián)類型的實際類型。關聯(lián)類型通過 associatedtype 關鍵字指定。

這里是一個叫做Container 的示例協(xié)議,聲明了一個叫做 ItemType 的關聯(lián)類型:

protocol Container {
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
}

Container 協(xié)議定義了兩個所有容器必須提供的功能:
1.必須能夠通過 append(_:) 方法向容器中添加新元素
2.必須能夠通過一個返回 Int 值的 count 屬性獲取容器中的元素數量

任何遵循 Container協(xié)議的類型必須能指定其存儲值的類型。尤其是它必須保證只有正確類型的元素才能添加到容器中。為了實現這些要求, Container 協(xié)議聲明了一個叫做 ItemType 的關聯(lián)類型,寫作 associatedtype ItemType

struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()

    // conformance to the Container protocol
    typealias ItemType = Int
    mutating func append(_ item: Int) {
        // append...
    }
    var count: Int {
        return items.count
    }
}

IntStack為了實現Container 協(xié)議,指定了適用于ItemType 的類型是 Int 類型。typealias ItemType = IntItemType 抽象類型轉換為了具體的 Int 類型。
如果你從代碼中刪除了typealias ItemType =Int,一切都會正常運行,因為 ItemType 會由Swift的類型推斷推斷出來。

subscript

下標的語法, 下標腳本允許你通過在實例名后面的方括號內寫一個或多個值對該類的實例進行查詢。它的語法類似于實例方法和和計算屬性。使用關鍵字 subscript 來定義下標,并且指定一個或多個輸入形式參數和返回類型,與實例方法一樣。與實例方法不同的是,下標可以是讀寫也可以是只讀的。這個行為通過與計算屬性中相同的 gettersetter 傳達:

subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }
}

newValue 的類型和下標的返回值一樣。與計算屬性一樣,你可以選擇不去指定setter的(newValue)形式參數。setter 默認提供形式參數 newValue ,如果你自己沒有提供的話。

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// prints "six times three is 18"

operator、prefix、postfix、infix

除了實現標準運算符,在 Swift 當中還可以聲明和實現自定義運算符(custom operators)。可以用來自定義運算符的字符列表請參考運算符
新的運算符要在全局作用域內,使用 operator 關鍵字進行聲明,同時還要指定 prefix 、infix 或者 postfix 限定符, 語法結構如下:

prefix operator `operatorName`
postfix operator `operatorName`
infix operator operatorname: `precedenceGroup`

上面的代碼定義了一個新的名為 +++ 的前綴運算符。這個運算符在 Swift 中并沒有意義,我們針對下面這個類SomeNumer的實例來賦予它意義。對這個例子來講, +++ 作為“平方”運算符。

prefix operator +++

class SomeNumber {
    var minNum = 0
    var maxNum = 0

    static prefix func +++(number: SomeNumber) -> SomeNumber {
        number.minNum = number.minNum * number.minNum
        number.maxNum = number.maxNum * number.maxNum
        return number
    }
}

var aaa = SomeNumber()
aaa.minNum = 3
aaa.maxNum = 6
+++aaa
print(aaa.minNum, aaa.maxNum)
// result is "9 36\n"

需要注意的地方是, 當使用自定義運算時, 傳入的參數至少要有一個當前對象, 否則編譯不會通過。定義前綴或后綴運算符時,不要指定優(yōu)先級。但是,如果將前綴和后綴運算符應用于相同的操作時,則首先進行后綴運算。

上面這個例子是前綴prefix, 當然后綴 postfix也是同樣的用法, 還有一個中綴 infix是比較特殊的, 涉及到結合性associativity和 優(yōu)先級precedence的使用。下面繼續(xù)來進行說明。

Swift 之關鍵字總結上篇
Swift 之關鍵字總結下篇

precedenceGroup、precedence(depricate from Swift 4.0)、associativity、left、right、none

自定義的中綴(infix)運算符也可以指定優(yōu)先級和結合性。優(yōu)先級和結合性中詳細闡述了這兩個特性是如何對中綴運算符的運算產生影響的。

以下示例定義了一個名為+ - 的新自定義中綴運算符,該運算符屬于優(yōu)先級組AdditionPrecedence:

infix operator +-: AdditionPrecedence
extension SomeNumber {
    static func +- (left: SomeNumber, right: SomeNumber) -> Int {
        return  left.minNum * left.maxNum + right.minNum * right.maxNum
    }
}
print(aaa +- aaa)
// result is 648

中綴的表達式中的precedenceGroup 是中綴運算符優(yōu)先級分組。優(yōu)先級組聲明 (A precedence group declaration) 會向程序的中綴運算符引入一個全新的優(yōu)先級組運算符的優(yōu)先級指定運算符在沒有分組括號的情況下綁定到其操作數的緊密程度。
自定義優(yōu)先級分組:

precedencegroup 優(yōu)先級組名稱{
    higherThan: 較低優(yōu)先級組的名稱
    lowerThan: 較高優(yōu)先級組的名稱
    associativity: 結合性
    assignment: 賦值性
}

較低優(yōu)先級組和較高優(yōu)先級組的名稱說明了新建的優(yōu)先級組是依賴于現存的優(yōu)先級組的。 lowerThan 優(yōu)先級組的屬性只可以引用當前模塊外的優(yōu)先級組。當兩個運算符為同一個操作數競爭時,比如表達式2 + 3 * 5,優(yōu)先級更高的運算符將優(yōu)先參與運算。

注意
使用較低和較高優(yōu)先級組相互聯(lián)系的優(yōu)先級組必須保持單一層次關系,但它們不必是線性關系。這意味著優(yōu)先級組也許會有未定義的相關優(yōu)先級。這些優(yōu)先級組的運算符在沒有用圓括號分組的情況下是不能緊鄰著使用的。

Swift定義了許多優(yōu)先組與標準庫提供的運算符一起使用。例如,加(+)和減( - )運算符屬于AdditionPrecedence組,乘(*)和除(/)運算符屬于MultiplicationPrecedence組。
運算符的結合性(associativity)表示在沒有圓括號分組的情況下,同樣優(yōu)先級的一系列運算符是如何被分組的。你可以指定運算符的結合性通過上下文關鍵字leftright或者none,如果沒有指定結合性,默認是none關鍵字。左關聯(lián)性的運算符是從左至右分組的,例如,相減操作符(-)是左關聯(lián)性的,所以表達式4 - 5 - 6被分組為(4 - 5) - 6,得出結果-7。右關聯(lián)性的運算符是從右往左分組的,指定為none結合性的運算符就沒有結合性。同樣優(yōu)先級沒有結合性的運算符不能相鄰出現,例如<運算符是none結合性,那表示1 < 2 < 3就不是一個有效表達式。

優(yōu)先級組的賦值性表示在包含可選鏈操作時的運算符優(yōu)先級。當設為true時,與優(yōu)先級組對應的運算符在可選鏈操作中使用和標準庫中賦值運算符同樣的分組規(guī)則,當設為false或者不設置,該優(yōu)先級組的運算符與不賦值的運算符遵循同樣的可選鏈規(guī)則。

defer

defer 語句用于在退出當前作用域之前執(zhí)行代碼。

defer {
    statement
}

defer 語句中的語句無論程序控制如何轉移都會被執(zhí)行。在某些情況下,例如,手動管理資源時,比如關閉文件描述符,或者即使拋出了錯誤也需要執(zhí)行一些操作時,就可以使用 defer 語句。

如果多個 defer 語句出現在同一作用域內,那么它們執(zhí)行的順序與出現的順序相反。給定作用域中的第一個 defer 語句,會在最后執(zhí)行,這意味著代碼中最靠后的 defer 語句中引用的資源可以被其他 defer 語句清理掉。

func f() {
    defer { print("First") }
    defer { print("Second") }
    defer { print("Third") }
}
f()
// 打印 “Third”
// 打印 “Second”
// 打印 “First”

defer 語句中的語句無法將控制權轉移到 defer 語句外部。

fallthrough

fallthrough 語句用于在 switch 語句中轉移控制權。fallthrough 語句會把控制權從 switch 語句中的一個 case 轉移到下一個 case。這種控制權轉移是無條件的,即使下一個 case 的模式與 switch 語句的控制表達式的值不匹配。

fallthrough 語句可出現在 switch 語句中的任意 case中,但不能出現在最后一個 case 中。同時,fallthrough 語句也不能把控制權轉移到使用了值綁定的 case

switch 1 {
case 1:
    print("111")
    fallthrough
case 2:
    print("222")
case 3:
    print("333")
default:
    print("default")
}
// result is 
// 111 
// 222

dynamicType (depricate from Swift 4.0)

注意: 這個關鍵字在Swift 4.0 開始已經廢棄了!!!
你可以對類型的實例使用 dynamicType 表達式來獲取該實例的動態(tài)運行時的類型。

class SomeBaseClass {
    class func printClassName() {
        print("SomeBaseClass")
    }
}
class SomeSubClass: SomeBaseClass {
    override class func printClassName() {
        print("SomeSubClass")
    }
}
let someInstance: SomeBaseClass = SomeSubClass()
// The compile-time type of someInstance is SomeBaseClass,
// and the runtime type of someInstance is SomeSubClass
someInstance.dynamicType.printClassName()
// Prints "SomeSubClass"

do 、 try 、 catch 、throw 、 throws、rethrows

表示錯誤

這些關鍵字都是關于錯誤處理的, 錯誤處理是相應和接收來自你程序中錯誤條件的過程。Swift 給運行時可恢復錯誤的拋出、捕獲、傳遞和操縱提供了一類支持。

在 Swift 中,錯誤表示為遵循 Error 協(xié)議類型的值。這個空的協(xié)議明確了一個類型可以用于錯誤處理。
Swift 枚舉是典型的為一組相關錯誤條件建模的完美配適類型,關聯(lián)值還允許錯誤錯誤通訊攜帶額外的信息。比如說,SomeError 的錯誤條件:

enum SomeError: Error {
    case SomeError1
    case SomeError2
    case SomeError3(code: Int)
}

拋出一個錯誤允許你明確某些意外的事情發(fā)生了并且正常的執(zhí)行流不能繼續(xù)下去。你可以使用 throw 語句來拋出一個錯誤。

throw SomeError.SomeError2
throw SomeError.SomeError3(code: value)

拋出錯誤

為了明確一個函數或者方法可以拋出錯誤,你要在它的聲明當中的形式參數后邊寫上throws關鍵字。使用 throws標記的函數叫做拋出函數。如果它明確了一個返回類型,那么 throws關鍵字要在返回箭頭 ( ->)之前。

func makeSomeError(value: Int)
func makeSomeError(value: Int) throws
func makeSomeError(value: Int) throws -> String

但是只有拋出函數可以傳遞錯誤。任何在非拋出函數中拋出的錯誤都必須在該函數內部處理。函數類型如果要拋出錯誤就必須使用 throws 關鍵字標記,而且能重拋錯誤的函數類型必須使用 rethrows 關鍵字標記。

完善這個可拋異常的函數實現:

func makeSomeError(value: Int) throws {
    switch value {
    case 1:
        throw SomeError.SomeError1
    case 2:
        throw SomeError.SomeError2
    case 3:
        throw SomeError.SomeError3(code: 888)
    case 4:
        // 默認的這里隨便找了一個錯誤, 來說明catch的范圍
        throw MachError(.exceptionProtected)
    default:
        print("excute normal code")
    }
}

處理錯誤

在 Swift 中有四種方式來處理錯誤。你可以將來自函數的錯誤傳遞給調用函數的代碼中,使用do-catch 語句來處理錯誤,把錯誤作為可選項的值,或者錯誤不會發(fā)生的斷言。

使用 do-catch語句來通過運行一段代碼處理錯誤。如果do分句中拋出了一個錯誤,它就會與catch分句匹配,以確定其中之一可以處理錯誤。

這是 do-catch語句的通常使用語法:

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}

按照上面的例子來寫, 如下:

do {
    try makeSomeError(value: 1)
} catch SomeError.SomeError1 {
    print("SomeError1")
} catch SomeError.SomeError2 {
    print("SomeError2")
} catch SomeError.SomeError3(let anyCode) {
    print("SomeError3 code is \(anyCode)")
}

結合三者, 就是一個完整的例子, 當我們執(zhí)行如上代碼時, 將會catch 到 SomeError1 并打印。
如果將 try 語句換為如下代碼時, 打印結果如下:

try makeSomeError(value: 3)
// SomeError2
try makeSomeError(value: 3)
// SomeError3 code is 888
try makeSomeError(value: 4)
// nothing print, because can't catch
try makeSomeError(value: 5)
// excute normal code

makeSomeError執(zhí)行 default分支時, 拋出的異常是不能 處理的, 因為catch中沒有涉及相關的異常所以catch不到的.

convenience

便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,并為其參數提供默認值。你也可以定義便利構造器來創(chuàng)建一個特殊用途或特定輸入值的實例。
如果你的類不需要便利構造器你可以不提供它。在為通用的初始化模式創(chuàng)建快捷方式以節(jié)省時間或者類的初始化更加清晰明了的時候時候便利構造器。
便利構造器可以將構造過程委托給另一個便利構造器或一個指定構造器。但是,類的構造過程必須以一個將類中所有屬性完全初始化的指定構造器的調用作為結束。便利構造器不能調用超類的構造器。

便利構造器有著相同的書寫方式,但是要用 convenience 修飾符放到 init 關鍵字前,用空格隔開:

convenience init(parameters) {
    statements
}
class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
var food = Food.init()
print(food.name)
// result is [Unnamed]

willSet、didSet

可以在聲明存儲型變量或屬性時提供 willSetdidSet 觀察器。一個包含觀察器的存儲型變量或屬性以如下形式聲明:

var 變量名稱: 類型 = 表達式 {  
    willSet(setter 名稱) {  
        語句
    }  
    didSet(setter 名稱) {  
        語句
    }  
}  

可以在全局范圍、函數內部,或者類、結構的聲明中使用這種形式的聲明。當變量以這種形式在全局范圍或者函數內部被聲明時,觀察器表示一個存儲型變量觀察器。當它在類和結構的聲明中被聲明時,觀察器表示一個屬性觀察器。
可以為任何存儲型屬性添加觀察器。也可以通過重寫父類屬性的方式為任何繼承的屬性(無論是存儲型還是計算型的)添加觀察器。

當變量或屬性的值被改變時,willSetdidSet 觀察器提供了一種觀察方法。觀察器會在變量的值被改變時調用,但不會在初始化時被調用。

willSet 觀察器只在變量或屬性的值被改變之前調用。新的值作為一個常量傳入 willSet 觀察器,因此不可以在 willSet 中改變它。didSet 觀察器在變量或屬性的值被改變后立即調用。和 willSet 觀察器相反,為了方便獲取舊值,舊值會傳入 didSet 觀察器。這意味著,如果在變量或屬性的 didiset 觀察器中設置值,設置的新值會取代剛剛在willSet觀察器中傳入的那個值。

willSetdidSet中,圓括號以及其中的 setter 名稱是可選的。如果提供了一個 setter 名稱,它就會作為 willSetdidSet 的參數被使用。如果不提供 setter 名稱,willSet 觀察器的默認參數名為 newValuedidSet 觀察器的默認參數名為 oldValue

提供了 willSet 時,didSet 是可選的。同樣的,提供了 didSet 時,willSet則是可選的。

open、public、internal、fileprivate、private

這些關鍵字是 Swift 為代碼的實體提供個五個不同的訪問級別。這些訪問級別和定義實體的源文件相關,并且也和源文件所屬的模塊相關。
open 訪問是最高的(限制最少)訪問級別,private是最低的(限制最多)訪問級別。

private

private 訪問, 將實體的使用限制于封閉聲明中。當一些細節(jié)僅在單獨的聲明中使用時,使用 private 訪問隱藏特定功能的實現細節(jié)。

fileprivate

File-private 訪問, 將實體的使用限制于當前定義源文件中。當一些細節(jié)在整個文件中使用時,使用 file-private 訪問隱藏特定功能的實現細節(jié)。

internal

Internal 訪問, 為默認訪問級別, 允許實體被定義模塊中的任意源文件訪問,但不能被該模塊之外的任何源文件訪問。通常在定義應用程序或是框架的內部結構時使用。

public、open

public 訪問和Open 訪問, 允許實體被定義模塊中的任意源文件訪問,同樣可以被另一模塊的源文件通過導入該定義模塊來訪問。在指定框架的公共接口時,通常使用 openpublic 訪問。
public 訪問只能在當前模塊中被繼承和子類重寫。
open 訪問僅適用于類和類成員,可以在其他模塊外被繼承和子類重寫。
顯式地標記類為 open 意味著你考慮過其他模塊使用該類作為父類對代碼的影響,并且相應地設計了類的代碼。

訪問控制的注意事項

Swift 中的訪問級別遵循一個總體指導準則:實體不可以被更低(限制更多)訪問級別的實體定義。

比如: 一個 public 的變量其類型的訪問級別不能是internal, file-private 或是private,因為在使用 public 變量的地方可能沒有這些類型的訪問權限。
又比如: 函數類型的訪問級別由函數成員類型和返回類型中的最嚴格訪問級別決定。一個函數不能比它的參數類型和返回類型訪問級別高,因為函數可以使用的環(huán)境而其參數和返回類型卻不能使用。

這里簡單列舉了兩個有關訪問級別的使用注意事項。想了解有更多詳細的注意事項的朋友, 可以查閱我的另外一篇博文: Swift 之訪問控制。這里面有代碼舉例和詳細說明, 小編這里不再贅述。

final

該修飾符用于修飾類或類中的屬性、方法以及下標。如果用它修飾一個類,那么這個類不能被繼承。如果用final 修飾類中的屬性、方法或下標,那么它們不能在子類中被重寫。

使用final的情況, 是類或方法屬性等不希望被繼承和重寫,具體情況一般是:
1.類或者方法的功能確實已經完備了, 基本不會再繼承和重寫。
2.避免子類繼承和修改造成危險。有些方法如果被子類繼承重寫會造成破壞性的后果,導致無法正常工作,則需要將其標為final加以保護。
3.保證父類的方法一定被執(zhí)行, 我們可以把父類的方法定義成final,同時將內部可以繼承的部分剝離出來,供子類繼承重寫。

還有一中說法, 認為final能改成性能,因為編譯器能從final中獲取額外的信息,所以可以對類或者方法調用進行優(yōu)化處理。其實這樣優(yōu)化對性能的提升非常有限,所以如果是為了提升性能, 把所有的屬性方法都加上final關鍵字,也沒有多大的作用。

required

必要構造器標識符, 修飾符用于修飾類的指定構造器或便利構造器,表示該類所有的子類都必須實現該構造器。在子類實現該構造器時,必須同樣使用 required 修飾符修飾該構造器。

為了要求子類去實現超類的構造器,使用 required 聲明修飾符標記超類的構造器。子類實現超類構造器時也必須使用required 聲明修飾符。

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

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

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

如果子類繼承的構造器能滿足必要構造器的要求,則無須在子類中顯式提供必要構造器的實現。就像下面的代碼, 因為子類繼承的構造器能滿足必要構造器的要求, 所以子類的必要構造器可以是隱性的。代碼如下:

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

}

你可以在遵循協(xié)議的類中實現構造器,無論是作為指定構造器,還是作為便利構造器。無論哪種情況,你都必須為構造器實現標上 required 修飾符:

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 這里是構造器的實現部分
    }
}

使用 required 修飾符可以確保所有子類也必須提供此構造器實現,從而也能符合協(xié)議。如果類已經被標記為 final,那么不需要在協(xié)議構造器的實現中使用 required 修飾符,因為 final 類不能有子類。如果這個類還沒有用 final 聲明修飾符標記,這個構造器必須用required聲明修飾符標記。
這就是為什么在日常開發(fā)中, 當我們繼承系統(tǒng)某各類去指定一個新的構造器時, 系統(tǒng)總是編譯報錯, 提示添加如下代碼:

required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

這種情況一般會出現在繼承了遵守NSCoding protocol的類,比如UIView系列的類、UIViewController系列的類。這是NSCoding protocol定義的,遵守了NSCoding protoaol的所有類必須繼承。當我們在子類定義了指定初始化器(包括自定義和重寫父類指定初始化器),那么必須顯示實現required init?(coder aDecoder: NSCoder),而其他情況下則會隱式繼承。

如果一個子類重寫了父類的指定構造器,并且該構造器滿足了某個協(xié)議的要求,那么該構造器的實現需要同時標注 requiredoverride 修飾符:

protocol SomeProtocol {
    init()
}
class SomeSuperClass {
    init() {
        // 這里是構造器的實現部分
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因為遵循協(xié)議,需要加上 required
    // 因為繼承自父類,需要加上 override
    required override init() {
        // 這里是構造器的實現部分
    }
}

mutating、nonmutating

結構體和枚舉是值類型。默認情況下,值類型屬性不能被自身的實例方法修改。但是,如果你確實需要在某個特定的方法中修改結構體或者枚舉的屬性,你可以為這個方法選擇可變(mutating)行為,然后就可以從其方法內部改變它的屬性;并且這個方法做的任何改變都會在方法執(zhí)行結束時寫回到原始結構中。方法還可以給它隱含的self屬性賦予一個全新的實例,這個新實例在方法結束時會替換現存實例。

結構體中用法:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

枚舉中用法:

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off

在協(xié)議中如何使用? 若你定義了一個協(xié)議的實例方法需求,想要改變任何采用了該協(xié)議的類型實例,只需在協(xié)議里方法的定義當中使用mutating 關鍵字。這允許結構體和枚舉類型能采用相應協(xié)議并滿足方法要求。

protocol Togglable {
    mutating func toggle()
}

Togglable協(xié)議的定義中, toggle() 方法使用 mutating 關鍵字標記,來表明該方法在調用時會改變遵循該協(xié)議的實例的狀態(tài):

struct Test: Togglable {
    var time: Int = 0

    mutating func toggle() {
        self.time = 33333
    }
}

var test = Test()
test.time = 2
test.toggle()
// result is 2

如果你在協(xié)議中標記實例方法需求為 mutating ,在為類實現該方法的時候不需要寫 mutating關鍵字。 mutating關鍵字只在結構體和枚舉類型中需要書寫。

dynamic

我來告訴你為什么Swift中要使用關鍵字dynamic。Swift 中的函數可以是靜態(tài)調用,靜態(tài)調用會更快。Swift的代碼直接被編譯優(yōu)化成靜態(tài)調用的時候,就不能從Objective-C 中的SEL字符串來查找到對應的IMP了。這樣就需要在 Swift 中添加一個關鍵字 dynamic,告訴編譯器這個方法是可能被動態(tài)調用的,需要將其添加到查找表中。

純Swift類沒有動態(tài)性,但在方法、屬性前添加dynamic修飾可以獲得動態(tài)性。該修飾符用于修飾任何兼容 Objective-C 的類的成員。訪問被 dynamic 修飾符標記的類成員將總是由 Objective-C 運行時系統(tǒng)進行動態(tài)派發(fā),而不會由編譯器進行內聯(lián)或消虛擬化。

繼承自NSObjectSwift類,其繼承自父類的方法具有動態(tài)性,其他自定義方法、屬性需要加dynamic修飾才可以獲得動態(tài)性。而且因為使用動態(tài)修飾符標記的聲明是使用Objective-C運行時分派的,所以它們必須用objc屬性標記。(從Swift 4.0開始, 加dynamic 修飾符時必須是顯式的objc了)

純Swift類中的dynamic的使用:

class DynamicSwiftClass {
    var zero = 0
    @objc dynamic var fist = 1
    @objc func dynamicFunc() {
    }
//    open this code will be error
//    @objc dynamic var adddd = (0 , 0)
//    @objc dynamic func someMethod(value: Int) -> (Int, Int) {
//        return (1, 1)
//    }
}

若方法的參數、屬性類型為Swift特有、無法映射到Objective-C的類型(如CharacterTuple),則此方法、屬性無法添加dynamic修飾, 一旦添加就會編譯報錯。

optional

該修飾符用于修飾協(xié)議中的屬性、方法以及下標成員,表示符合類型可以不實現這些成員要求。

可選類型

//    swift中nil也是一種特殊的類型,不同類型之間不能進行賦值,swift是強類型語言,所以推出來可選類型 。對象中的任意屬性都必需有初始化值
//    >方式1常規(guī)方式(不常用)
    var name : Optional<String> = nil
//    >方式2語法糖(常用)
    var name1:String? = nil
//    可選類型打印Optional("obj")\n
//    取出可選類型的值:強制解包 !.強制解包是非常微信的操作,如果可選類型為nil,強制解包會導致系統(tǒng)崩潰
//在強制解包前先對可選類型進行判斷判斷是否為nil
        print(name!)
        if name != nil {
            print(name!)
        }
        //        可選綁定:
// let newName = name       1>判斷name是否有值,如果沒有值直接不賦值給newName  2>如果name有值,系統(tǒng)會自動將name進行解包,并且將解包的結果,賦值給newName
//        寫法1
        if let newName = name {
            print(newName)
        }
//        寫法2
        if let name = name {
            print(newName)
        }

Swift 為命名類型 Optional<Wrapped>定義后綴 ? 作為語法糖 ,其定義在 Swift 標準庫中。換句話說,下列兩種聲明是等價的:

var optionalInteger: Int? 
var optionalInteger: Optional<Int>

在上述兩種情況下,變量 optionalInteger 都聲明為可選整數類型。注意在類型和 ? 之間沒有空格。
類型 Optional<Wrapped> 是有兩種情況的, noneSome(Wrapped),它代表可能沒有值或可能有值。任何類型都可以被顯式的聲明(或隱式的轉換)為可選類型。如果你在聲明可選的變量或屬性時沒有提供初始值,它的值則會默認為 nil
使用 ! 操作符獲解析一個值為 nil 的可選項會導致運行時錯誤。你也可以使用可選鏈和可選綁定來有條件地執(zhí)行對可選表達式的操作。如果值為 nil ,不會執(zhí)行任何操作并且不會因此產生運行時錯誤。

隱式展開可選類型

Swift 為命名類型 Optional<Wrapped> 定義后綴 !作為語法糖 ,其定義在 Swift 標準庫中,作為它被訪問時自動解析的附加行為。如果你試圖使用一個值為 nil 的隱式解析,你會得到一個運行時錯誤。除了隱式展開的行為之外,下面兩個聲明是等價的:

var implicitlyUnwrappedString: String! 
var explicitlyUnwrappedString: Optional<String>

注意類型與! 之間沒有空格。
有了可選項,如果在聲明隱式展開可選變量或屬性時你不用提供初始值,它的值會默認為 nil 。使用可選鏈有條件地對隱式展開可選項的表達式進行操作。如果值為 nil,就不執(zhí)行任何操作,因此也不會產生運行錯誤。

可選的協(xié)議

協(xié)議可以定義可選要求,遵循協(xié)議的類型可以選擇是否實現這些要求。在協(xié)議中使用 optional 關鍵字作為前綴來定義可選要求。可選要求用在你需要和 Objective-C 打交道的代碼中。協(xié)議和可選要求都必須帶上objc屬性。標記 objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 objc 類遵循,其他類以及結構體和枚舉均不能遵循這種協(xié)議。只能將 optional修飾符用于被 objc特性標記的協(xié)議。這樣一來,就只有類類型可以采納并符合擁有可選成員要求的協(xié)議。

使用可選要求時(例如,可選的方法或者屬性),它們的類型會自動變成可選的。比如,一個類型為 (Int) -> String 的方法會變成((Int) -> String)?。需要注意的是整個函數類型是可選的,而不是函數的返回值。

協(xié)議中的可選要求可通過可選鏈式調用來使用,因為遵循協(xié)議的類型可能沒有實現這些可選要求。類似 someOptionalMethod?(someArgument) 這樣,你可以在可選方法名稱后加上 ? 來調用可選方法。

@objc protocol CounterDataSource {
    @objc optional var fixedIncrement: Int { get }
    @objc optional func incrementForCount() -> Int
}

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.incrementForCount?() {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12

當沒有代理沒有實現可選的協(xié)議時, dataSource?.incrementForCount?()nil, 只有當代理實現了此協(xié)議方法時, amount才會是返回的那個值。

indirect

遞歸枚舉是一種枚舉類型,它有一個或多個枚舉成員使用該枚舉類型的實例作為關聯(lián)值。使用遞歸枚舉時,編譯器會插入一個間接層。你可以在枚舉成員前加上indirect來表示該成員可遞歸, 而且被 indirect 修飾符標記的枚舉用例必須有一個關聯(lián)值。。

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

你也可以在枚舉類型開頭加上indirect關鍵字來表明它的所有成員都是可遞歸的:

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

上面定義的枚舉類型可以存儲三種算術表達式:純數字、兩個表達式相加、兩個表達式相乘。枚舉成員additionmultiplication的關聯(lián)值也是算術表達式。

下面的代碼展示了使用ArithmeticExpression這個遞歸枚舉創(chuàng)建表達式(5 + 4) * 2:

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

使用這個枚舉:

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}
print(evaluate(product))

原文:https://blog.csdn.net/wangyanchang21/article/details/78887137
版權聲明:本文為博主原創(chuàng)文章,轉載請附上博文鏈接!
Swift 之關鍵字總結上篇
Swift 之關鍵字總結下篇

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 我跟北京有將近七年的緣分,從06年大學入學,到13年研究生畢業(yè)。自從畢業(yè)之后,我一次也沒有回去過,幾乎也沒有想念過...
    想嚎叫的熊閱讀 140評論 0 0
  • 大晚上吃了檳榔,感覺來勁啦,哈哈哈哈哈
    愛無痕丶閱讀 163評論 0 0