Swift-27個關鍵字,助力開發(篇幅有點長)

一、柯里化(Currying)

Swift 裏能夠將方法進行柯里化 (Currying),也就是把接受多個參數的方法變換成接受第一個參數的方法,而且返回接受餘下的參數而且返回結果的新方法git

func add(_ v1:Int,_ v2:Int) -> Int {
    return v1 + v2
}
print(add(1, 2))


//柯里化(Currying)
func add(_ v:Int) -> (Int) -> Int {
    return {$0 + v}
}
print(add(1)(2))

複製代碼

二、mutating

Swift 的 protocol 不只能夠被 class類型實現,也適用於 struct 和 enum程序員

Swift 的 mutating 關鍵字修飾方法是爲了能在該方法中修改 struct 或是 enum 的變量,因此若是你沒在協議方法裏寫 ``mutating 的話,別人若是用 struct 或者 enum 來實現這個協議的話,就不能在方法裏改變本身的變量了github

在使用 class 來實現帶有 mutating的方法的協議時,具體實現的前面是不須要加 mutating修飾的,由於 class 能夠隨意更改本身的成員變量。因此說在協議裏用 mutating修飾方法,對於 class 的實現是徹底透明,能夠看成不存在的編程

protocol Vehicle {
   var numberOfWheels:Int{get}
   mutating func changeNumberOfWheels()
}

struct MyCar:Vehicle {
   var numberOfWheels: Int = 4
   
   mutating func changeNumberOfWheels() {
       numberOfWheels = 4
   }
}

class Cars: Vehicle {
   var numberOfWheels: Int = 0
   func changeNumberOfWheels() {
       numberOfWheels = 2
   }
}
複製代碼

三、Sequence

Sequence 是一系列相同類型的值的集合,而且提供對這些值的迭代能力。swift

迭代一個Sequence最多見的方式就是 for-in 循環api

let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
   print(animal)
}
複製代碼

Sequence 協議的定義數組

protocol Sequence {
   associatedtype Iterator: IteratorProtocol
   func makeIterator() -> Iterator
}
複製代碼

Sequence 協議只有一個必須實現的方法 makeIterator()性能優化

makeIterator() 須要返回一個 Iterator,它是一個 IteratorProtocol 類型。bash

也就是說只要提供一個Iterator 就能實現一個 Sequence,那麼 Iterator 又是什麼呢?網絡

Iterator Iterator 在 Swift 3.1 標準庫中即爲 IteratorProtocol,它用來爲 Sequence 提供迭代能力。對於 Sequence,咱們能夠用 for-in 來迭代其中的元素,其實 for-in 的背後是 IteratorProtocol 在起做用

IteratorProtocol 的定義以下:

public protocol IteratorProtocol {
   associatedtype Element
   public mutating func next() -> Self.Element?
}
複製代碼

對於這個for...in循環

let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
   print(animal)
}
複製代碼

實際上編譯器會把以上代碼轉換成下面的代碼

var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
    print(animal)
}
複製代碼
  • 一、獲取到 animals 數組的 Iterator
  • 二、在一個 while 循環中,經過 Iterator 不斷獲取下一個元素,並對元素進行操做
  • 三、當 next() 返回 nil 時,退出循環

實現一個逆序

//咱們先實現一個IteratorProtocol協議類型
class ReverseIterator<T>: IteratorProtocol {
   typealias Element = T
   var array: [Element]
   var currentIndex = 0

   init(array: [Element]) {
       self.array = array
       currentIndex = array.count - 1
   }
   func next() -> Element? {
       if currentIndex < 0{
           return nil
       }
       else {
           let element = array[currentIndex]
           currentIndex -= 1
           return element
       }
   }
}

// 而後咱們來定義 Sequence
struct ReverseSequence<T>:Sequence {
   var array:[T]
   init (array: [T]) {
       self.array = array
   }
   typealias Iterator = ReverseIterator<T>
   func makeIterator() -> ReverseIterator<T> {
       return ReverseIterator(array: self.array)
   }
}

for item in ReverseSequence(array: animals){
   print(item)
}

複製代碼

參考:Swift 中的 Sequence(一)

四、元組(Tuple)

元組是swift編程語言中惟一的一種複合類型,他能夠將指定有限個數的任何類型一次整理爲一個對象,元組中的每一種類型均可以是任何的結構體、枚舉或類類型,甚至也能夠是一個元組以及空元組。

好比交換輸入,普通程序員亙古以來可能都是這麼寫的

func swapMel1<T>(a:inout T, b:inout T) {
    let temp = a
    a = b
    b = temp
}
複製代碼

可是要是使用多元組的話,咱們能夠不使用額外空間就完成交換,一會兒就達到了文藝程序員的寫法

func swapMel2<T>(a:inout T, b:inout T) {
   (a,b) = (b,a)
}
複製代碼

五、自動閉包(@autoclosure)

自動閉包是一種自動建立的用來把做爲實際參數傳遞給函數的表達式打包的閉包。它不接受任何實際參數,而且當它被調用時,它會返回內部打包的表達式的值

這個語法的好處在於經過寫普通表達式代替顯式閉包而使你省略包圍函數形式參數的括號

func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)


func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive2(1, 2) //這個報錯
getFirstPositive2(1, {2})

func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
複製代碼
  • @autoclosure會自動的將2封裝爲{2}

  • @autoclosure只支持() -> T的格式參數

??

在 Swift 中,有一個很是有用的操做符,能夠用來快速地對 nil 進行條件判斷,那就是 ??。這個操做符能夠判斷輸入並在當左側的值是非 nil 的 Optional 值時返回其 value,當左側是 nil 時返回右側的值

??就是一個@autoclosure

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
複製代碼

咱們來猜想一下??的實現

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
   switch optional {
       case .Some(let value):
           return value
       case .None:
           return defaultValue()
       }
}
複製代碼

可能你會有疑問,爲何這裏要使用autoclosure,直接接受T 做爲參數並返回不行麼,爲什麼要用 () -> T 這樣的形式包裝一遍,豈不是多此一舉?其實這正是 autoclosure 的一個最值得稱讚的地方。若是咱們直接使用T,那麼就意味着在 ?? 操做符真正取值以前,咱們就必須準備好一個默認值傳入到這個方法中,通常來講這不會有很大問題,可是若是這個默認值是經過一系列複雜計算獲得的話,可能會成爲浪費 -- 由於其實若是optional 不是 nil 的話,咱們其實是徹底沒有用到這個默認值,而會直接返回optional 解包後的值的。這樣的開銷是徹底能夠避免的,方法就是將默認值的計算推遲到 optional 斷定爲 nil 以後

在 Swift 中,其實 &&|| 這兩個操做符裏也用到了 @autoclosure

public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool

public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
複製代碼

六、逃逸閉包(@escaping )與非逃逸閉包(@noescaping)

逃逸閉包(@escaping )

當閉包做爲一個實際參數傳遞給一個函數的時候,咱們就說這個閉包逃逸了,由於它是在函數返回以後調用的。當你聲明一個接受閉包做爲形式參數的函數時,你能夠在形式參數前寫 @escaping 來明確閉包是容許逃逸的。

閉包能夠逃逸的一種方法是被儲存在定義於函數外的變量裏。好比說,不少函數接收閉包實際參數來做爲啓動異步任務的回調。函數在啓動任務後返回,可是閉包要直到任務完成——閉包須要逃逸,以便於稍後調用

例如:當網絡請求結束後調用的閉包。發起請求後過了一段時間後這個閉包才執行,並不必定是在函數做用域內執行的

override func viewDidLoad() {
        super.viewDidLoad()
         
        getData { (data) in
            print("閉包返回結果:\(data)")
        }
    }

    func getData(closure:@escaping (Any) -> Void) {
        print("函數開始執行--\(Thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("執行了閉包---\(Thread.current)")
                closure("345")
            })
        }
        print("函數執行結束---\(Thread.current)")
    }
複製代碼

從結果能夠看出,逃逸閉包的生命週期是長於函數的。

逃逸閉包的生命週期:

  • 一、閉包做爲參數傳遞給函數;
  • 二、退出函數;
  • 三、閉包被調用,閉包生命週期結束

即逃逸閉包的生命週期長於函數,函數退出的時候,逃逸閉包的引用仍被其餘對象持有,不會在函數結束時釋放。

非逃逸閉包(@noescaping)

一個接受閉包做爲參數的函數, 閉包是在這個函數結束前內被調用。

override func viewDidLoad() {
        super.viewDidLoad()
         
        handleData { (data) in
            print("閉包返回結果:\(data)")
        }
    }

    func handleData(closure:(Any) -> Void) {
        print("函數開始執行--\(Thread.current)")
        print("執行了閉包---\(Thread.current)")
        closure("123")
        print("函數執行結束---\(Thread.current)")
    }
複製代碼

爲何要分逃逸閉包和非逃逸閉包

爲了管理內存,閉包會強引用它捕獲的全部對象,好比你在閉包中訪問了當前控制器的屬性、函數,編譯器會要求你在閉包中顯示 self 的引用,這樣閉包會持有當前對象,容易致使循環引用。

非逃逸閉包不會產生循環引用,它會在函數做用域內釋放,編譯器能夠保證在函數結束時閉包會釋放它捕獲的全部對象;使用非逃逸閉包的另外一個好處是編譯器能夠應用更多強有力的性能優化,例如,當明確了一個閉包的生命週期的話,就能夠省去一些保留(retain)和釋放(release)的調用;此外非逃逸閉包它的上下文的內存能夠保存在棧上而不是堆上。

七、操做符

與 Objective-C 不一樣,Swift 支持重載操做符這樣的特性,最多見的使用方式可能就是定義一些簡便的計算了

系統操做符

好比咱們須要一個表示二維向量的數據結構

struct Vector2D {
   var x:CGFloat = 0
   var y:CGFloat = 0
}
複製代碼

一個很簡單的需求是兩個 Vector2D 相加

let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)
let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)  
複製代碼

若是隻作一次的話彷佛還好,可是通常狀況咱們會進行不少這種操做。這樣的話,咱們可能更願意定義一個 Vector2D 相加的操做,來讓代碼簡化清晰

func +(left:Vector2D,right:Vector2D) -> Vector2D {
   Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v3 = v1 + v2
複製代碼

八、自定義操做符

在Swift語言中,常見的操做符有+、-、*、/、>、<、==、&&、||等等,若是不喜歡,你也能夠定義本身喜歡的操做符。

  • precedencegroup:定義操做符的優先級
  • associativity:操做符的結合律
  • higherThanlowerThan:運算符的優先級
  • prefix、infix、postfix:前綴、中綴、後綴運算符

中綴

/// 定義優先級組
precedencegroup MyPrecedence {
    // higherThan: AdditionPrecedence   // 優先級,比加法運算高
    lowerThan: AdditionPrecedence       // 優先級, 比加法運算低
    associativity: none                 // 結合方向:left, right or none
    assignment: false                   // true=賦值運算符,false=非賦值運算符
}

infix operator +++: MyPrecedence        // 繼承 MyPrecedence 優先級組
// infix operator +++: AdditionPrecedence // 也能夠直接繼承加法優先級組(AdditionPrecedence)或其餘優先級組
func +++(left: Int, right: Int) -> Int {
    return left+right*2
}
 
print(2+++3) // 8
複製代碼

前綴

prefix operator ==+
prefix func ==+(left: Int) -> Int {
   
   return left*2
}
print(==+2) // 4
複製代碼

後綴

postfix operator +==
postfix func +==(right: Int) -> Int {
   
   return right*3
}
print(2+==) // 6
複製代碼

九、inout:輸入輸出參數

可變形式參數只能在函數的內部作改變。若是你想函數可以修改一個形式參數的值,並且你想這些改變在函數結束以後依然生效,那麼就須要將形式參數定義爲輸入輸出形式參數。

在形式參數定義開始的時候在前邊添加一個 inout關鍵字能夠定義一個輸入輸出形式參數。輸入輸出形式參數有一個能輸入給函數的值,函數能對其進行修改,還能輸出到函數外邊替換原來的值。

你只能把變量做爲輸入輸出形式參數的實際參數。你不能用常量或者字面量做爲實際參數,由於常量和字面量不能修改。在將變量做爲實際參數傳遞給輸入輸出形式參數的時候,直接在它前邊添加一個和符號 (&) 來明確能夠被函數修改。

var b = 10
func test(a:inout Int) {
   a = 20
}
test(a: &b)
print(b) //20
複製代碼

能夠用inout定義一個輸入輸出參數:能夠在函數內部修改外部實參的值

  • 一、不可變參數不能標記爲inout
  • 二、inout參數不能有默認值
  • 三、inout參數只能傳入能夠被屢次賦值的
  • 四、inout參數的本質就是地址傳遞

十、下標

下標相信你們都很熟悉了,在絕大多數語言中使用下標來讀寫相似數組或者是字典這樣的數據結構的作法,彷佛已是業界標準。在 Swift 中,Array 和 Dictionary 固然也實現了下標讀寫

var arr = [1,2,3]
arr[2]            // 3
arr[2] = 4        // arr = [1,2,4]

var dic = ["cat":"meow", "goat":"mie"]
dic["cat"]          // {Some "meow"}
dic["cat"] = "miao" // dic = ["cat":"miao", "goat":"mie"]  
複製代碼

做爲一門表明了先進生產力的語言,Swift 是容許咱們自定義下標的,咱們找到Array已經支持的下標類型

subscript (index: Int) -> T
subscript (subRange: Range<Int>) -> Slice<T>
複製代碼

咱們發現若是咱們想要取出0、二、4下標值,咱們須要循環枚舉。

其實這裏有一個更好的作法,好比能夠實現一個接受數組做爲下標輸入的讀取方法

extension Array {
   subscript(input: [Int]) -> ArraySlice<Element> {
       get {
           var result = ArraySlice<Element>()
           for i in input {
               assert(i < self.count, "Index out of range")
               result.append(self[i])
           }
           return result
       }
       set {
           for (index,i) in input.enumerated() {
               assert(i < self.count, "Index out of range")
               self[i] = newValue[index]
           }
       }
   }
}

var arr = ["a","b","c","d","z"]
print(arr[[0,3]]) //["a", "d"]
複製代碼

十一、嵌套函數

咱們能夠把函數當成參數或者變量來使用,函數內部嵌套函數

func forward(_ forward:Bool) -> (Int) -> Int {
    
    func next(_ input:Int) -> Int {
        input + 1
    }

    func previous(_ input:Int) -> Int {
        input - 1
    }
    
    return forward ? next : previous
}
複製代碼

十二、命名空間

Objective-C 一個一直以來使人詬病的地方就是沒有命名空間,在應用開發時,全部的代碼和引用的靜態庫最終都會被編譯到同一個域和二進制中。這樣的後果是一旦咱們有重複的類名的話,就會致使編譯時的衝突和失敗。爲了不這種事情的發生,Objective-C 的類型通常都會加上兩到三個字母的前綴,好比 Apple 保留的 NS 和 UI 前綴,各個系統框架的前綴 SK (StoreKit),CG (CoreGraphic) 等。Objective-C 社區的大部分開發者也遵照了這個約定,通常都會將本身名字縮寫做爲前綴,把類庫命名爲 AFNetworking 或者 MBProgressHUD 這樣。這種作法能夠解決部分問題,至少咱們在直接引用不一樣人的庫時衝突的機率大大下降了,可是前綴並不意味着不會衝突,有時候咱們確實仍是會遇到即便使用前綴也仍然相同的狀況。另一種狀況是可能你想使用的兩個不一樣的庫,分別在它們裏面引用了另外一個相同的很流行的第三方庫,而又沒有更更名字。在你分別使用「這兩個庫中的一個時是沒有問題的,可是一旦你將這兩個庫同時加到你的項目中的話,這個你們共用的第三方庫就會和本身發生衝突了。

在 Swift 中,因爲可使用命名空間了,即便是名字相同的類型,只要是來自不一樣的命名空間的話,都是能夠和平共處的。和 C# 這樣的顯式在文件中指定命名空間的作法不一樣,Swift 的命名空間是基於 module 而不是在代碼中顯式地指明,每一個 module 表明了 Swift 中的一個命名空間。也就是說,同一個 target 裏的類型名稱仍是不能相同的。在咱們進行 app 開發時,默認添加到 app 的主 target 的內容都是處於同一個命名空間中的,咱們能夠經過建立 Cocoa (Touch) Framework 的 target 的方法來新建一個 module,這樣咱們就能夠在兩個不一樣的 target 中添加一樣名字的類型了

1三、typealias別名

咱們能夠給一個複雜的難以理解的類型起一個別名,方便咱們使用和理解

按照swift標準庫的定義Void就是一個空元組

public typealias Void = ()
複製代碼

咱們知道swift中沒有byte、short、Long類型,若是咱們想要這樣的類型,就能夠用typealias實現

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
複製代碼

咱們還能夠給函數起一個別名

typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int) -> Int {
   v1 - v2
}
let fn:IntFn = difference
print(fn(2,1))  //1
複製代碼

咱們還能夠給元組起別名

typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date) {
   print(date.year)
}
test((2019,10,30))
複製代碼

1四、associatedtype

咱們在 Swift 協議中能夠定義屬性和方法,並要求知足這個協議的類型實現它們:

protocol Food { }

protocol Animal {
   func eat(_ food: Food)
}

struct Meat: Food { }
struct Grass: Food { }
複製代碼
struct Tiger: Animal {
   func eat(_ food: Food) {

   }
}
複製代碼

由於老虎並不吃素,因此在 Tiger 的 eat 中,咱們極可能須要進行一些轉換工做才能使用 meat

associatedtype 聲明中可使用冒號來指定類型知足某個協議

protocol Animal {
   associatedtype F: Food
   func eat(_ food: F)
}

struct Tiger: Animal {
   func eat(_ food: Meat) {
       print("eat \(meat)")
   }
}

struct Sheep: Animal {
   func eat(_ food: Grass) {
       print("eat \(food)")
   }
} 
複製代碼

不過在添加associatedtype 後,Animal 協議就不能被看成獨立的類型使用了。試想咱們有一個函數來判斷某個動物是否危險:

func isDangerous(animal: Animal) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false
   }
}
複製代碼

會報錯

Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements

這是由於 Swift 須要在編譯時肯定全部類型,這裏由於 Animal 包含了一個不肯定的類型,因此隨着 Animal 自己類型的變化,其中的F 將沒法肯定 (試想一下若是在這個函數內部調用 eat的情形,你將沒法指定 eat 參數的類型)。在一個協議加入了像是 associatedtype 或者 Self 的約束後,它將只能被用爲泛型約束,而不能做爲獨立類型的佔位使用,也失去了動態派發的特性。也就是說,這種狀況下,咱們須要將函數改寫爲泛型

func isDangerous<T: Animal>(animal: T) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false
   }
}

isDangerous(animal: Tiger()) // true
複製代碼

1五、可變參數

一個可變形式參數能夠接受零或者多個特定類型的值,可變參數必須是同一類型的。當調用函數的時候你能夠利用可變形式參數來聲明形式參數能夠被傳入值的數量是可變的。能夠經過在形式參數的類型名稱後邊插入三個點符號(...)來書寫可變形式參數。

func sum(_ numbers:Int...) -> Int{
   var total = 0
   for item in numbers {
       total += item
   }
   return total
}
複製代碼

Swift自帶的print函數

/// - Parameters:
///   - items: Zero or more items to print.
///   - separator: A string to print between each item. The default is a single
///     space (`" "`).
///   - terminator: The string to print after all items have been printed. The
///     default is a newline (`"\n"`).
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
複製代碼
  • 一、第一個參數:就是須要打印的值,是一個可變參數
  • 二、第二個參數:兩個打印值鏈接的地方,默認是空格
  • 三、第三個參數:結尾默認是\n換行

1六、初始化

初始化是爲類、結構體或者枚舉準備實例的過程。這個過須要給實例裏的每個存儲屬性設置一個初始值而且在新實例可使用以前執行任何其餘所必須的配置或初始化

你經過定義初始化器來實現這個初始化過程,它更像是一個用來建立特定類型新實例的特殊的方法。不一樣於 Objective-C 的初始化器,Swift 初始化器不返回值。這些初始化器主要的角色就是確保在第一次使用以前某類型的新實例可以正確初始化。

類有兩種初始化器

  • 一、指定初始化器(designated initializer)
  • 二、便捷初始化器(convenience initializer)
class Person {
   var age: Int
   var name: String
   
   //指定初始化器
   init(age:Int, name:String) {
       self.age = age
       self.name = name
   }
   //便捷初始化器
   convenience init(age:Int){
       self.init(age:age,name:"")
   }
}
複製代碼
  • 一、每個類至少有一個指定初始化器,指定初始化器是類的最主要初始化器
  • 二、默認初始化器老是類的指定初始化器
  • 三、類偏向於少許指定初始化器,一個類一般就只有一個指定初始化器

初始化器的相互調用規則

  • 一、指定初始化器必須從他的直系父類調用指定初始化器
  • 二、便捷初始化器必須從相同的類裏調用另外一個初始化器
  • 三、便捷初始化器最終必須調用一個指定初始化器

1七、Static & Class

Swift 中表示 「類型範圍做用域」 這一律念有兩個不一樣的關鍵字,它們分別是 staticclass。這兩個關鍵字確實都表達了這個意思

非class的類型上下文中,咱們統一使用 static 來描述類型做用域

1八、default 參數

Swift 的方法是支持默認參數的,也就是說在聲明方法時,能夠給某個參數指定一個默認使用的值。在調用該方法時要是傳入了這個參數,則使用傳入的值,若是缺乏這個輸入參數,那麼直接使用設定的默認值進行調用

func test(a:String = "1",b:String,c:String = "3"){}
複製代碼

1九、匹配模式

什麼是模式 模式是用於匹配的規則,好比switchcase、捕獲錯誤的catchif\guard\while\for語句

Swift中模式有

  • 一、通配符模式(Wildcard Pattern)
  • 二、標識符模式(Identifier Pattern)
  • 三、值綁定模式(Value-Binding Pattern)
  • 四、元祖模式(Tuple Pattern)
  • 五、枚舉Case模式(Enumeration Case Pattern)
  • 六、可選模式(Optional Pattern)
  • 七、類型轉換模式(Type-Casting Pattern)
  • 八、表達式模式(Expression Pattern)

通配符模式(Wildcard Pattern)

  • _ 匹配任何值
  • _?匹配非nil值
enum Life {
    case human(name:String,age:Int?)
    case animal(name:String,age:Int?)
}

func check(_ life:Life){
    switch life {
    case .human(let name,_):
        print("human:",name)
    case .animal(let name,_?):
        print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小紅", age: nil))//human: 小紅
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
複製代碼

標識符模式(Identifier Pattern)

就是給對應的變量常亮賦值

let a = 10
let b = "text"
複製代碼

值綁定模式(Value-Binding Pattern)

let point = (2,3)
switch point {
case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

複製代碼

元祖模式(Tuple Pattern)

匹配任何元祖

let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
    print(x)
}
//0
//1
//2
複製代碼

枚舉Case模式(Enumeration Case Pattern)

if case語句等價於一個caseswitch語句,簡化了一些判斷語句

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0,9]")
}
//枚舉模式
if case 0...9 = age{
    print("[0,9]")
}
複製代碼
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
    print("有nil值")
}
複製代碼

可選模式(Optional Pattern)

let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
    print(age)
}
// 2
//3
複製代碼

等價於

let ages:[Int?] = [nil,2,3,nil]
for item in ages{
    if let age = item {
        print(age)
    }
}

複製代碼

類型轉換模式(Type-Casting Pattern)

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
複製代碼

表達式模式(Expression Pattern)

能夠經過重載運算符,自定義表達式模式的匹配規則

struct Student {
    var score = 0, name = ""
    static func ~=(pattern:Int,value:Student) -> Bool{
        value.score >= pattern
    }
    
    static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    static func ~=(pattern:Range<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
複製代碼
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
    print("以j開頭,或者以k結尾")
default:
    break
}
複製代碼

20、... 和 ..<

Range 操做符,用來簡單地指定一個從 X 開始連續計數到 Y 的範圍

咱們能夠仔細看看 Swift 中對着兩個操做符的定義

/// Forms a closed range that contains both `minimum` and `maximum`.
func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
        -> Range<Pos>

/// Forms a closed range that contains both `start` and `end`.
/// Requres: `start <= end`
func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
        -> Range<Pos>


/// Forms a half-open range that contains `minimum`, but not
/// `maximum`.
func ..<<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos)
        -> Range<Pos>

/// Forms a half-open range that contains `start`, but not
/// `end`.  Requires: `start <= end`
func ..<<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos)
        -> Range<Pos>


/// Returns a closed interval from `start` through `end`
func ...<T : Comparable>(start: T, end: T) -> ClosedInterval<T>
  
  
 /// Returns a half-open interval from `start` to `end`
func ..<<T : Comparable>(start: T, end: T) -> HalfOpenInterval<T>  
複製代碼

不難發現,其實這幾個方法都是支持泛型的。除了咱們經常使用的輸入 Int 或者 Double,返回一個 Range 之外,這個操做符還有一個接受 Comparable 的輸入,並返回 ClosedInterval 或 HalfOpenInterval 的重載

好比想確認一個單詞裏的所有字符都是小寫英文字母的話,能夠這麼作

let test = "helLo"
let interval = "a"..."z"
for c in test {
    if !interval.contains(String(c)) {
        print("\(c) 不是小寫字母")
    }
}

// 輸出
// L 不是小寫字母
複製代碼

2一、AnyClass,元類型和 .self

在 Swift 中可以表示 「任意」 這個概念的除了 Any 和 AnyObject 之外,還有一個 AnyClassAnyClass 在 Swift 中被一個 typealias 所定義

public typealias AnyClass = AnyObject.Type
複製代碼

經過 AnyObject.Type 這種方式所獲得是一個元類型 (Meta)。在聲明時咱們老是在類型的名稱後面加上 .Type,好比 A.Type 表明的是 A 這個類型的類型。也就是說,咱們能夠聲明一個元類型來存儲 A 這個類型自己,而在從A中取出其類型時,咱們須要使用到 .self

其實在 Swift 中,.self 能夠用在類型後面取得類型自己,也能夠用在某個實例後面取得這個實例自己。前一種方法能夠用來得到一個表示該類型的值,這在某些時候會頗有用;然後者由於拿到的實例自己

class A {
    class func method() {
        print("Hello")
    }
}

let typeA: A.Type = A.self
typeA.method()

// 或者
let anyClass: AnyClass = A.self
(anyClass as! A.Type).method() 
複製代碼

也許你會問,這樣作有什麼意義呢,咱們難道不是能夠直接使用 A.method() 來調用麼?沒錯,對於單個獨立的類型來講咱們徹底沒有必要關心它的元類型,可是元類型或者元編程的概念能夠變得很是靈活和強大,這在咱們編寫某些框架性的代碼時會很是方便。好比咱們想要傳遞一些類型的時候,就不須要不斷地去改動代碼了。在下面的這個例子中雖然咱們是用代碼聲明的方式獲取了 MusicViewController 和 AlbumViewController 的元類型,可是其實這一步驟徹底能夠經過讀入配置文件之類的方式來完成的。而在將這些元類型存入數組而且傳遞給別的方法來進行配置這一點上,元類型編程就很難被替代了

class MusicViewController: UIViewController {

}

class AlbumViewController: UIViewController {

}

let usingVCTypes: [AnyClass] = [MusicViewController.self,
    AlbumViewController.self]

func setupViewControllers(_ vcTypes: [AnyClass]) {
    for vcType in vcTypes { 
            if vcType is UIViewController.Type {
            let vc = (vcType as! UIViewController.Type).init()
            print(vc)
        }

    }
}

setupViewControllers(usingVCTypes) 
複製代碼

這麼一來,咱們徹底能夠搭好框架,而後用 DSL 的方式進行配置,就能夠在不觸及 Swift 編碼的狀況下,很簡單地完成一系列複雜操做了

2二、動態類型

Swift 中咱們雖然能夠經過 dynamicType 來獲取一個對象的動態類型 (也就是運行時的實際類型,而非代碼指定或編譯器看到的類型)。可是在使用中,Swift 如今倒是不支持多方法的,也就是說,不能根據對象在動態時的類型進行合適的重載方法調用

class Pet {}
class Dog:Pet {}
class Cat:Pet {}
 
func eat(_ pet:Pet) {
    print("pet eat")
}

func eat(_ dog:Dog) {
    print("dog eat")
}

func eat(_ cat:Cat) {
    print("cat eat")
}
 
func eats(_ pet:Pet,_ cat:Cat) {
    eat(pet)
    eat(cat)
}
eats(Dog(), Cat())
//pet eat
//cat eat
複製代碼

咱們在打印Dog類型信息的時候,並無被用來在運行時選擇合適的func eat(_ dog:Dog) {}方法,而是被忽略了,並採用了編譯期間決定的Pet方法。

由於 Swift 默認狀況下是不採用動態派發的,所以方法的調用只能在編譯時決定

要想繞過這個限制,咱們可能須要進行經過對輸入類型作判斷和轉換

func eats(_ pet:Pet,_ cat:Cat) {
    if let aCat = pet as? Cat {
        eat(aCat)
    }else if let aDog = pet as? Cat{
        eat(aDog)
    }
    eat(cat)
}
複製代碼

2三、屬性

在Swift中所聲明的屬性包括

  • 一、存儲屬性:存儲屬性將會在內存中實際分配地址進行屬性的存儲
  • 二、計算屬性:計算屬性則不包括存儲,只是提供setget方法

存儲屬性

咱們能夠在存儲屬性中提供了willSetdidSet兩種屬性觀察方法

class Person {
    var age:Int = 0{
        willSet{
            print("即將將年齡從\(age)設置爲\(newValue)")
        }
        didSet{
            print("已經將年齡從\(oldValue)設置爲\(age)")
        }
    }
}

let p = Person()
p.age = 10
//即將將年齡從0設置爲10
//已經將年齡從0設置爲10

複製代碼

willSetdidSet中咱們分別可使用newValueoldValue 來獲取將要設定的和已經設定的值。

初始化方法對屬性的設定,以及在 willSet 和 didSet 中對屬性的再次設定都不會再次觸發屬性觀察的調用

計算屬性

在 Swift 中所聲明的屬性包括存儲屬性計算屬性兩種。其中存儲屬性將會在內存中實際分配地址對屬性進行存儲,而計算屬性則不包括背後的存儲,只是提供setget 兩種方法。在同一個類型中,屬性觀察和計算屬性是不能同時共存的。也就是說,想在一個屬性定義中同時出現 setwillSetdidSet 是一件辦不到的事情。

計算屬性中咱們能夠經過改寫 set 中的內容來達到和willSetdidSet 一樣的屬性觀察的目的。若是咱們沒法改動這個類,又想要經過屬性觀察作一些事情的話,可能就須要子類化這個類,而且重寫它的屬性

重寫的屬性並不知道父類屬性的具體實現狀況,而只從父類屬性中繼承名字和類型,所以在子類的重載屬性中咱們是能夠對父類的屬性任意地添加屬性觀察的,而不用在乎父類中究竟是存儲屬性仍是計算屬性

class A {
    var number:Int {
        get{
            print("get")
            return 1
        }
        set{
            print("set")
        }
    }
}

class B:A {
    override var number: Int{
        willSet{
            print("willSet")
        }
        didSet{
            print("didSet")
        }
    }
}

let b = B()
b.number = 10

get
willSet
set
didSet
複製代碼

set 和對應的屬性觀察的調用都在咱們的預想之中。這裏要注意的是 get 首先被調用了一次。這是由於咱們實現了 didSetdidSet 中會用到 oldValue,而這個值須要在整個 set 動做以前進行獲取並存儲待用,不然將沒法確保正確性。若是咱們不實現 didSet 的話,此次 get操做也將不存在。

2四、lazy修飾符

延時加載或者說延時初始化是很經常使用的優化方法,在構建和生成新的對象的時候,內存分配會在運行時耗費很多時間,若是有一些對象的屬性和內容很是複雜的話,這個時間更是不可忽略。另外,有些狀況下咱們並不會當即用到一個對象的全部屬性,而默認狀況下初始化時,那些在特定環境下不被使用的存儲屬性,也同樣要被初始化和賦值,也是一種浪費

swift提供了一個關鍵字lazy

class A {
    lazy var str:String = {
        let str = "Hello"
        print("首次訪問的時候輸出")
        return str
    }()
}

let a = A()
print(a.str)
複製代碼

爲了簡化,咱們若是不須要作什麼額外工做的話,也能夠對這個 lazy 的屬性直接寫賦值語句

lazy var str1 = "word"
複製代碼

2五、Reflection 和 Mirror

Objective-C 中咱們不太會常常說起到 「反射」 這樣的詞語,由於 Objective-C 的運行時比通常的反射還要靈活和強大。可能不少讀者已經習覺得常的像是經過字符串生成類或者 selector,而且進而生成對象或者調用方法等,其實都是反射的具體的表現。而在 Swift 中其實就算拋開 Objective-C 的運行時的部分,在純 Swift 範疇內也存在有反射相關的一些內容,只不過相對來講功能要弱得多。

Swift提供了Mirror類型來作映射的事情

struct Person {
    let name:String
    let age:Int
}

let xiaoming = Person(name: "小明", age: 10)
let r = Mirror(reflecting: xiaoming)

print("xiaoming是\(r.displayStyle)")
print("屬性個數:\(r.children.count)")

for child in r.children{
    print("屬性名:\(child.label) 值:\(child.value)")
}


xiaoming是Optional(Swift.Mirror.DisplayStyle.struct)
屬性個數:2
屬性名:Optional("name") 值:小明
屬性名:Optional("age") 值:10
複製代碼

經過 Mirror 初始化獲得的結果中包含的元素的描述都被集合在children 屬性下

public typealias Child = (label: String?, value: Any)
public typealias Children = AnyCollection<Mirror.Type.Child> 
複製代碼

若是以爲一個個打印太過於麻煩,咱們也能夠簡單地使用 dump 方法來經過獲取一個對象的鏡像並進行標準輸出的方式將其輸出出來

print(dump(xiaoming))

▿ 反射.Person
  - name: "小明"
  - age: 10
Person(name: "小明", age: 10)
複製代碼

常見的應用場景是相似對 Swift 類型的對象作像 Objective-C 中 KVC 那樣的 valueForKey: 的取值。經過比較取到的屬性的名字和咱們想要取得的 key 值就好了,很是簡單

func valueFrom(_ object: Any, key: String) -> Any? {
    let mirror = Mirror(reflecting: object)
    for child in mirror.children {
        let (targetKey, targetMirror) = (child.label, child.value)
        if key == targetKey {
            return targetMirror
        }
    }
    return nil
}


if let name = valueFrom(xiaoming, key: "name") as? String {
    print("經過 key 獲得值: \(name)")
}

經過 key 獲得值: 小明
複製代碼

在如今的版本中,Swift 的反射特性並非很是強大,咱們只能對屬性進行讀取,還不能對其設定。 另外須要特別注意的是,雖然理論上將反射特性應用在實際的 app 製做中是可行的,可是這一套機制設計的最初目的是用於 REPL 環境和 Playground 中進行輸出的。因此咱們最好遵照 Apple 的這一設定,只在 REPL 和 Playground 中用它來對一個對象進行深層次的探索,而避免將它用在 app 製做中 -- 由於你永遠不知道何時它們就會失效或者被大幅改動

2六、可選值(optional)

咱們點擊進入官方文檔,能夠看到optional是一個枚舉

enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
    public init(_ some: Wrapped)
}
複製代碼

咱們來看看下面這兩個值是否相同anotherString literalString

var string:String? = "string"
var anotherString:String?? = string
var literalString:String?? = "string"

print(anotherString)
print(literalString)
print(anotherString == literalString)

//Optional(Optional("string"))
//Optional(Optional("string"))
//true
複製代碼
  • 一、var anotherString:String?? = string的寫法其實就是Optional.some(string)
  • 二、var literalString:String?? = "string"的寫法是Optional.some(Optional.some("string"))

二者的返回值是同樣的,因此二者相等

下面兩個對象是否相等呢?

var aNil:String? = nil
var anoterNil:String?? = aNil
var literalNil:String?? = nil
print(anoterNil)
print(literalNil)
print(anoterNil == literalNil)
//Optional(nil)
//nil
//false
複製代碼
  • 一、anoterNil是可選項裏面包含一個可選項值爲nil的可選項
  • 二、literalNil是可選擇值爲nil的可選項

咱們可使用fr v -R來打印具體信息

1

2七、匹配模式

什麼是模式 模式是用於匹配的規則,好比switchcase、捕獲錯誤的catchif\guard\while\for語句

Swift中模式有

  • 一、通配符模式(Wildcard Pattern)
  • 二、標識符模式(Identifier Pattern)
  • 三、值綁定模式(Value-Binding Pattern)
  • 四、元祖模式(Tuple Pattern)
  • 五、枚舉Case模式(Enumeration Case Pattern)
  • 六、可選模式(Optional Pattern)
  • 七、類型轉換模式(Type-Casting Pattern)
  • 八、表達式模式(Expression Pattern)

通配符模式(Wildcard Pattern)

  • _ 匹配任何值
  • _?匹配非nil值
enum Life {
    case human(name:String,age:Int?)
    case animal(name:String,age:Int?)
}

func check(_ life:Life){
    switch life {
    case .human(let name,_):
        print("human:",name)
    case .animal(let name,_?):
        print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "小明", age: 20)) //human: 小明
check(.human(name: "小紅", age: nil))//human: 小紅
check(.animal(name: "dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
複製代碼

標識符模式(Identifier Pattern)

就是給對應的變量常亮賦值

let a = 10
let b = "text"
複製代碼

值綁定模式(Value-Binding Pattern)

let point = (2,3)
switch point {
case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

複製代碼

元祖模式(Tuple Pattern)

匹配任何元祖

let point = [(0,0),(1,0),(2,0)]
for (x,_) in point{
    print(x)
}
//0
//1
//2
複製代碼

枚舉Case模式(Enumeration Case Pattern)

if case語句等價於一個caseswitch語句,簡化了一些判斷語句

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0,9]")
}
//枚舉模式
if case 0...9 = age{
    print("[0,9]")
}
複製代碼
let ages:[Int?] = [2,3,nil,5]
for case nil in ages{
    print("有nil值")
}
複製代碼

可選模式(Optional Pattern)

let ages:[Int?] = [nil,2,3,nil]
for case let age? in ages{
    print(age)
}
// 2
//3
複製代碼

等價於

let ages:[Int?] = [nil,2,3,nil]
for item in ages{
    if let age = item {
        print(age)
    }
}

複製代碼

類型轉換模式(Type-Casting Pattern)

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
複製代碼

表達式模式(Expression Pattern)

能夠經過重載運算符,自定義表達式模式的匹配規則

struct Student {
    var score = 0, name = ""
    static func ~=(pattern:Int,value:Student) -> Bool{
        value.score >= pattern
    }
    
    static func ~=(pattern:ClosedRange<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    static func ~=(pattern:Range<Int>,value:Student) -> Bool{
        pattern.contains(value.score)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print(">=100")
case 90:print(">=90")
case 80..<90:print("[80,90]")
case 60...79:print("[60,79]")
default:break
}
複製代碼
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

func hasPrefix(_ prefix:String) ->((String) -> Bool){{$0.hasPrefix(prefix)}}
func hasSuffix(_ prefix:String) ->((String) -> Bool){{$0.hasSuffix(prefix)}}



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k"):
    print("以j開頭,或者以k結尾")
default:
    break
}
複製代碼

總結

  • 一、文章中代碼的demo地址
  • 二、文章是讀王巍 (onevcat). 「Swifter - Swift 必備 Tips (第四版)總結所得
相關文章
相關標籤/搜索