Swift進階黃金之路(一)

Swift進階黃金之路(二)

這篇是對一文鑑定是Swift的王者,還是青銅文章中問題的解答。這些問題僅僅是表層概念,屬於知識點,在我看來即使都很清楚也並不能代表上了王者,如果非要用段位類比的話,黃金還是合理的😄。

Swift是一門上手容易,但是精通較難的語言。即使下面這些內容都不清楚也不妨礙你開發業務需求,但是瞭解之後它能夠幫助我們寫出更加Swifty的代碼。

一、 協議 Protocol

ExpressibleByDictionaryLiteral

ExpressibleByDictionaryLiteral是字典的字面量協議,該協議的完整寫法爲:

public protocol ExpressibleByDictionaryLiteral {
    /// The key type of a dictionary literal.
    associatedtype Key
    /// The value type of a dictionary literal.
    associatedtype Value
    /// Creates an instance initialized with the given key-value pairs.
    init(dictionaryLiteral elements: (Self.Key, Self.Value)...)
}
複製代碼

首先字面量(Literal)的意思是:用於表達源代碼中一個固定值的表示法(notation)

舉個例子,構造字典我們可以通過以下兩種方式進行:

// 方法一:
var countryCodes = Dictionary<String, Any>()
countryCodes["BR"] = "Brazil"
countryCodes["GH"] = "Ghana"
// 方法二:
let countryCodes = ["BR": "Brazil", "GH": "Ghana"]
複製代碼

第二種構造方式就是通過字面量方式進行構造的。

其實基礎類型基本都是通過字面量進行構造的:

let num: Int = 10
let flag: Bool = true
let str: String = "Brazil"
let array: [String] = ["Brazil", "Ghana"]
複製代碼

而這些都有對應的字面量協議:

ExpressibleByNilLiteral // nil字面量協議
ExpressibleByIntegerLiteral // 整數字面量協議
ExpressibleByFloatLiteral // 浮點數字面量協議
ExpressibleByBooleanLiteral // 布爾值字面量協議
ExpressibleByStringLiteral // 字符串字面量協議
ExpressibleByArrayLiteral // 數組字面量協議
複製代碼

Sequence

Sequence翻譯過來就是序列,該協議的目的是一系列相同類型的值的集合,並且提供對這些值的迭代能力,這裏的迭代可以理解爲遍歷,也即for-in的能力。可以看下該協議的定義:

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

Sequence又引入了另一個協議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)
}
複製代碼

這裏的for-in會被編譯器翻譯成:

var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
    print(animal)
}
複製代碼

Collection

Collection譯爲集合,其繼承於Sequence。

public protocol Collection : Sequence {
  associatedtype Index : Comparable
  var startIndex: Index { get }
  var endIndex: Index { get }
  var isEmpty: Bool { get }
  var count: Int { get }
  
  subscript(position: Index) -> Element { get }
  subscript(bounds: Range<Index>) -> SubSequence { get }
}
複製代碼

是一個元素可以反覆遍歷並且可以通過索引的下標訪問的有限集合,注意Sequence可以是無限的,Collection必須是有限的。

CollectionSequence的基礎上擴展了下標訪問、元素個數能特性。我們常用的集合類型ArrayDictionarySet都遵循該協議。

CustomStringConvertible

這個協議表示自定義類型輸出的樣式。先來看下它的定義:

public protocol CustomStringConvertible {
    var description: String { get }
}
複製代碼

只有一個description的屬性。它的使用很簡單:

struct Point: CustomStringConvertible {
    let x: Int, y: Int
    var description: String {
        return "(\(x), \(y))"
    }
}

let p = Point(x: 21, y: 30)
print(p) // (21, 30)
//String(describing: <#T##CustomStringConvertible#>)
let s = String(describing: p)
print(s) // (21, 30)
複製代碼

如果不實現CustomStringConvertible,直接打印對象,系統會根據默認設置進行輸出。我們可以通過CustomStringConvertible對這一輸出行爲進行設置,還有一個協議是CustomDebugStringConvertible

public protocol CustomDebugStringConvertible {
    var debugDescription: String { get }
}
複製代碼

CustomStringConvertible用法一樣,對應debugPrint的輸出。

Hashable

我們常用的DictionarySet均實現了Hashable協議。Hash的目的是爲了將查找集合某一元素的時間複雜度降低到O(1),爲了實現這一目的需要將集合元素與存儲地址之間建議一種儘可能一一對應的關係。

我們再看Hashable`協議的定義:

public protocol Hashable : Equatable {
    var hashValue: Int { get }
    func hash(into hasher: inout Hasher)
}

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}
複製代碼

注意到func hash(into hasher: inout Hasher),Swift 4.2 通過引入 Hasher 類型並採用新的通用哈希函數進一步優化 Hashable

如果你要自定義類型實現 Hashable 的方式,可以重寫 hash(into:) 方法而不是 hashValuehash(into:) 通過傳遞了一個 Hasher 引用對象,然後通過這個對象調用 combine(_:) 來添加類型的必要狀態信息。

// Swift >= 4.2
struct Color: Hashable {
    let red: UInt8
    let green: UInt8
    let blue: UInt8

    // Synthesized by compiler
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.red)
        hasher.combine(self.green)
        hasher.combine(self.blue)
    }

    // Default implementation from protocol extension
    var hashValue: Int {
        var hasher = Hasher()
        self.hash(into: &hasher)
        return hasher.finalize()
    }
}
複製代碼

參考:Hashable / Hasher

Codable

Codable是可DecodableEncodable的類型別名。它能夠將程序內部的數據結構序列化成可交換數據,也能夠將通用數據格式反序列化爲內部使用的數據結構,大大提升對象和其表示之間互相轉換的體驗。處理的問題就是我們經常遇到的JSON轉模型,和模型轉JSON。

public typealias Codable = Decodable & Encodable

public protocol Decodable {
    init(from decoder: Decoder) throws
}
public protocol Encodable {
    func encode(to encoder: Encoder) throws
}
複製代碼

這裏只舉一個簡單的解碼過程:

//json數據
{
    "id": "1283984",
    "name": "Mike",
    "age": 18
}
// 定義對象
struct Person: Codable{
    var id: String
    var name: String
    var age: Int
}
// json爲網絡接口返回的Data類型數據
let mike = try! JSONDecoder().decode(Person.self, from: json)
print(mike)
//輸出:Student(id: "1283984", name: "Mike", age: 18)
複製代碼

是不是非常簡單,Codable還支持各種自定義解編碼過程,完全可以取代SwiftyJSONHandyJSON等編解碼庫。

Comparable

這個是用於實現比較功能的協議,它的定義如下:

public protocol Comparable : Equatable {
  
    static func < (lhs: Self, rhs: Self) -> Bool

    static func <= (lhs: Self, rhs: Self) -> Bool

    static func >= (lhs: Self, rhs: Self) -> Bool

    static func > (lhs: Self, rhs: Self) -> Bool
}
複製代碼

其繼承於Equatable,即判等的協議。可以很清楚的理解實現了各種比較的定義就具有了比較的功能。這個不做比較。

RangeReplaceableCollection

RangeReplaceableCollection支持用另一個集合的元素替換元素的任意子範圍的集合。

看下它的定義:

public protocol RangeReplaceableCollection : Collection where Self.SubSequence : RangeReplaceableCollection {

    associatedtype SubSequence
  
    mutating func append(_ newElement: Self.Element)
    mutating func insert<S>(contentsOf newElements: S, at i: Self.Index) where S : Collection, Self.Element == S.Element
    /* 拼接、插入、刪除、替換的方法,他們都具有對組元素的操作能力 */
  
    override subscript(bounds: Self.Index) -> Self.Element { get }
    override subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get }
}
複製代碼

舉個例子,Array支持該協議,我們可以進行如下操作:

var bugs = ["Aphid", "Damselfly"]
bugs.append("Earwig")
bugs.insert(contentsOf: ["Bumblebee", "Cicada"], at: 1)
print(bugs)
// Prints "["Aphid", "Bumblebee", "Cicada", "Damselfly", "Earwig"]"
複製代碼

這裏附一張Swift中Array遵循的協議關係圖,有助於大家理解上面講解的幾個協議之間的關係:

圖像來源:swiftdoc.org/v3.1/type/a…

二、@propertyWrapper

閱讀以下代碼,print 輸出什麼

@propertyWrapper
struct Wrapper<T> {
    var wrappedValue: T

    var projectedValue: Wrapper<T> { return self }

    func foo() { print("Foo") }
}
struct HasWrapper {
    @Wrapper var x = 0
    
    func foo() {
        print(x) // 0
        print(_x) // Wrapper<Int>(wrappedValue: 0)
        print($x) // Wrapper<Int>(wrappedValue: 0)
     }
}
複製代碼

這段代碼看似要考察對@propertyWrapper的理解,但是有很多無用內容,導致代碼很奇怪。

@propertyWrapper的意思就是屬性包裝,它可以將一系列相似的屬性方法進行統一處理。舉個例子,如果我們需要在UserDefaults中加一個是否首次啓動的值,正常可以這樣處理:

extension UserDefaults {
    enum Keys {
      static let isFirstLaunch = "isFirstLaunch"
    }
    var isFirstLaunch: Bool {
        get {
            return bool(forKey: Keys.isFirstLaunch)
        }
        set {
            set(newValue, forKey: Keys.isFirstLaunch)
        }
    }
}
複製代碼

如果我們需要加入很多這樣屬性的話,就需要寫大量的getset方法。而@propertyWrapper的作用就是爲屬性的這種設置提供一個模板寫法,以下是使用屬性包裝的寫法。

@propertyWrapper
struct UserDefaultWrapper<T> {
    private let key: String
    private let defaultValue: T
    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
  
    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

class UserDefaults {
    @UserDefaultWrapper(key: Keys.isFirstLaunch, defaultValue: false)
    var isFirstLaunch: Bool
}

複製代碼

@propertyWrapper約束的對象必須要定義wrappedValue屬性,因爲該對象包裹的屬性會走到wrappedValue的實現。

回到實例代碼,定義了wrappedValue卻並沒有添加任何實現,這是允許的。所以訪問x的時候其實是訪問WrapperwrappedValue,因爲沒有給出任何實現所以直接打印出0。而_x$x對應的就是Wrapper自身。

參考:Swift Property Wrappers

三、關鍵字

public open

public open爲權限關鍵詞。對於一個嚴格的項目來說,精確的最小化訪問控制級別對於代碼的維護來說相當重要的。完整的權限關鍵詞,按權限大小排序如下:

open > public > internal > fileprivate > private

  • open權限最大,允許外部module訪問,繼承,重寫。
  • public允許外部module訪問,但不允許繼承,重寫。
  • internal爲默認關鍵詞,在同一個module內可以共用。
  • fileprivate表示代碼可以在當前文件中被訪問,而不做類型限定。
  • private表示代碼只能在當前作用域或者同一文件中同一類型的作用域中被使用。

這些權限關鍵詞可以修飾,屬性,方法和類型。需要注意:當一個類型的某一屬性要用public修飾時,該類型至少要用public(或者open)權限的關鍵詞修復。可以理解爲數據訪問是分層的,我們爲了獲取某一屬性或方法需要先獲取該類型,所以外層(類型)的訪問權限要滿足大於等於內層(類型、方法、屬性)權限。

參考:Swift AccessControl

static class final

原文中final跟權限關鍵詞放在一起了,其實是不合理的,就將其放到這裏來討論。

static靜態變量關鍵詞,來源於C語言。

在Swift中常用語以下場景:

// 僅用於類名前,表示該類不能被繼承。僅支持class類型
final class Manager {
    // 單例的聲明
    static let shared = Manager()
    // 實例屬性,可被重寫
    var name: String = "Ferry"
    // 實例屬性,不可被重寫
    final var lastName: String = "Zhang"
    // 類屬性,不可被重寫
    static var address: String = "Beijing"
    // 類屬性,可被重寫。注意只能作爲計算屬性,而不能作爲存儲屬性
    class var code: String {
        return "0122"
    }
  
    // 實例函數,可被重寫
    func download() {
      /* code... */
    }
    // 實例函數,不可被重寫
    final func download() {
      /* code... */
    }
    // 類函數,可被重寫
    class func removeCache() {
     	/* code... */ 
    }
    // 類函數,不可被重寫
    static func download() {
      /* code... */
    }
}

struct Manager {
    // 單例的聲明
    static let shared = Manager()
    // 類屬性
    static var name: String = "Ferry"
    // 類函數
    static func download() {
      /* code... */
    }
}
複製代碼

structenum因爲不能被繼承,所以也就無法使用classfinal關鍵詞,僅能通過static關鍵詞進行限定

mutating inout

mutating用於修飾會改變該類型的函數之前,基本都用於struct對象的修改。看下面例子:

struct Point {
    var x: CGFloat
    var y: CGFloat
    // 因爲該方法改變了struct的屬性值(x),所以必須要加上mutating
    mutating func moveRight(offset: CGFloat) {
        x += offset
    }

    func normalSwap(a: CGFloat, b: CGFloat) {
        let temp = a
        a = b
        b = temp
    }
    // 將兩個值交換,需傳入對象地址。注意inout需要加載類型名前
    func inoutSwap(a: inout CGFloat, b: inout CGFloat) {
        let temp = a
        a = b
        b = temp
    }
}

var location1: CGFloat = 10
var location2: CGFloat = -10

var point = Point.init(x: 0, y: 0)
point.moveRight(offset: location1)
print(point)	//Point(x: 10.0, y: 0.0)

point.normalSwap(a: location1, b: location2)
print(location1)	//10
print(location2)	//-10
// 注意需帶取址符&
point.inoutSwap(a: &location1, b: &location2)
print(location1)	//-10
print(location2)	//10
複製代碼

inout需要傳入取值符,所以它的改變會導致該對象跟着變動。可以再回看上面說的Hashable的一個協議實現:

func hash(into hasher: inout Hasher) {
    hasher.combine(self.red)
    hasher.combine(self.green)
    hasher.combine(self.blue)
}
複製代碼

只有使用inout才能修改傳入的hasher的值。

infix operator

infix operator即爲中綴操作符,還有prefix、postfix後綴操作符。

它的作用是自定義操作符。比如Python裏可以用**進行冪運算,但是Swift裏面,我們就可以利用自定義操作符來定義一個用**實現的冪運算。

// 定義中綴操作符
infix operator **
// 實現該操作符的邏輯,中綴需要兩個參數
func ** (left: Double, right: Double) -> Double {
    return pow(left, right)
}
let number = 2 ** 3
print(value) //8
複製代碼

同理我們還可以定義前綴和後綴操作符:

//定義階乘操作,後綴操作符
postfix operator ~!
postfix func ~! (value: Int) -> Int {

    func factorial(_ value: Int) -> Int {
        if value <= 1 {
            return 1
        }
        return value * factorial(value - 1)
    }
    return factorial(value)
}
//定義輸出操作,前綴操作符
prefix operator <<
prefix func << (value: Any) {
    print(value)
}

let number1 = 4~!
print(number1) // 24

<<number1 // 24
<<"zhangferry" // zhangferry
複製代碼

前綴和後綴僅需要一個操作數,所以只有一個參數即可。

關於操作符的更多內容可以查看這裏:Swift Operators

注意,因爲該文章較早,其中對於操作符的一些定義已經改變。

@dynamicMemberLookup,@dynamicCallable

這兩個關鍵詞我確實沒有用過,看到dynamic可以知道這兩個特性是爲了讓Swift具有動態性。

@dynamicMemberLookup中文叫動態查找成員。在使用@dynamicMemberLookup標記了對象後(對象、結構體、枚舉、protocol),實現了subscript(dynamicMember member: String)方法後我們就可以訪問到對象不存在的屬性。如果訪問到的屬性不存在,就會調用到實現的 subscript(dynamicMember member: String)方法,key 作爲 member 傳入這個方法。 舉個例子:

@dynamicMemberLookup
struct Person {
    subscript(dynamicMember member: String) -> String {
        let properties = ["nickname": "Zhuo", "city": "Hangzhou"]
        return properties[member, default: "undefined"]
    }
}
//執行以下代碼
let p = Person()
print(p.city)	//Hangzhou
print(p.nickname)	//Zhuo
print(p.name)	//undefined
複製代碼

我們沒有定義Person的citynicknamename屬性,卻可以用點語法去嘗試訪問它。如果沒有@dynamicMemberLookup這種寫法會被編譯器檢查出來並報錯,但是加了該關鍵詞編譯器就不會管它是不是存在都予以通過。

@dynamicCallable
struct Person {
    // 實現方法一
    func dynamicallyCall(withArguments: [String]) {
        for item in withArguments {
            print(item)
        }
    }
    // 實現方法二
    func dynamicallyCall(withKeywordArguments: KeyValuePairs<String, String>){
        for (key, value) in withKeywordArguments {
            print("\(key) --- \(value)")
        }
    }
}
let p = Person()
p("zhangsan")
// 等於 p.dynamicallyCall(withArguments: ["zhangsan"])
p("zhangsan", "20", "男")
// 等於 p.dynamicallyCall(withArguments: ["zhangsan", "20", "男"])
p(name: "zhangsan")
// 等於 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan"])
p(name: "zhangsan", age:"20", sex: "男")
// 等於 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan", "age": "20", "sex": "男"])
複製代碼

@dynamicCallable可以理解成動態調用,當爲某一類型做此聲明時,需要實現dynamicallyCall(withArguments:)或者dynamicallyCall(withKeywordArguments:)。編譯器將允許你調用併爲定義的方法。

一個動態查找成員變量,一個動態方法調用,帶上這兩個特性Swift就可以變成徹頭徹尾的動態語言了。所以作爲靜態語言的Swift也是可以具有動態特性的。

更多關於這兩個動態標記的討論可以看卓同學的這篇:細說 Swift 4.2 新特性:Dynamic Member Lookup

where

where一般用作條件限定。它可以用在for-in、swith、do-catch中:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for item in numbers where item % 2 == 1 {
    print("odd: \(item)")	// 將輸出1,3,5,7,9等數
}

numbers.forEach { (item) in
    switch item {
    case let x where x % 2 == 0:
        print("even: \(x)") // 將輸出2,4,6,8等數
    default:
        break
    }
}
複製代碼

where也可以用於類型限定。

我們可以擴展一個字典的merge函數,它可以將兩個字典進行合併,對於相同的Key值以要合併的字典爲準。並且該方法我只想針對KeyValue都是String類型的字典使用,就可以這麼做:

// 這裏的Key Value來自於Dictionary中定義的泛型
extension Dictionary where Key == String, Value == String {
    //同一個key操作覆蓋舊值
    func merge(other: Dictionary) -> Dictionary {
        return self.merging(other) { _, new in new }
    }
}
複製代碼

@autoclosure

@autoclosure 是使用在閉包類型之前,做的事情就是把一句表達式自動地封裝成一個閉包 (closure)。

比如我們有一個方法接受一個閉包,當閉包執行的結果爲 true 的時候進行打印,分別使用普通閉包和加上autoclosure的閉包實現:

func logIfTrueNormal(predicate: () -> Bool) {
    if predicate() {
        print("True")
    }
}
// 注意@autoclosure加到閉包的前面
func logIfTrueAutoclosure(predicate: @autoclosure () -> Bool) {
    if predicate() {
        print("True")
    }
}
// 調用方式
logIfTrueNormal(predicate: {3 > 1})
logIfTrueAutoclosure(predicate: 3 > 1)
複製代碼

編譯器會將logIfTrueAutoclosure函數參數中的3 > 1這個表達式轉成{3 > 1}這種尾隨閉包樣式。

那這種寫法有什麼用處呢?我們可以從一個示例中體會一下,在Swift系統提供的幾個短路運算符(即表達式左邊如果已經確定結果,右邊將不再運算)中均採用了@autoclosure標記的閉包。那??運算符舉例,它的實現是這樣的:

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}
// 使用
var name: String? = "ferry"
let currentName = name ?? getDefaultName()
複製代碼

因爲使用了@autoclosure標記閉包,所以??defaultValue參數我們可以使用表達式,又因爲是閉包,所以當name非空時,直接返回了該值,不會調用getDefaultName()函數,減少計算。

參考:@AUTOCLOSURE 和 ??,注意因爲Swift版本問題,實例代碼無法運行。

@escaping

@escaping也是閉包修飾詞,用它標記的閉包被稱爲逃逸閉包,還有一個關鍵詞是@noescape,用它修飾的閉包叫做非逃逸閉包。在Swift3及之後的版本,閉包默認爲非逃逸閉包,在這之前默認閉包爲逃逸閉包。

這兩者的區別主要在於聲明週期的不同,當閉包作爲參數時,如果其聲明週期與函數一致就是非逃逸閉包,如果聲明週期大於函數就是逃逸閉包。結合示例來理解:

// 非逃逸閉包
func logIfTrueNormal(predicate: () -> Bool) {
    if predicate() {
        print("True")
    }
}
// 逃逸閉包
func logIfTrueEscaping(predicate: @escaping () -> Bool) {
    DispatchQueue.main.async {
        if predicate() {
            print("True")
        }
    }
}
複製代碼

第二個函數的閉包爲逃逸閉包是因爲其是異步調用,在函數退出時,該閉包還存在,聲明週期長於函數。

如果你無法判斷出應該使用逃逸還是非逃逸閉包,也無需擔心,因爲編譯器會幫你做出判斷。第二個函數,如果我們不聲明逃逸閉包編譯器會報錯,警告我們:Escaping closure captures non-escaping parameter 'predicate'。當然我們還是應該理解兩者的區別。

四、高階函數

Filter, Map, Reduce, flatmap, compactMap

這幾個高階函數都是對數組對象使用的,我們通過示例去了解他們吧:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// filter 過濾
let odd = numbers.filter { (number) -> Bool in
    return number % 2 == 1
}
print(odd) // [1, 3, 5, 7, 9]

//map 轉換
let maps = odd.map { (number) -> String in
    return "\(number)"
}
print(maps) // ["1", "3", "5", "7", "9"]

// reduce 累計運算
let result = odd.reduce(0, +)
print(result) // 25

// flatMap 1.數組展開
let numberList = [[1, 2, 3], [4, 5], [[6]]]
let flatMapNumber = numberList.flatMap { (value) in
    return value
}
print(flatMapNumber) // [1, 2, 3, 4, 5, [6]]

// flatMap 2.過濾數組中的nil
let country = ["cn", "us", nil, "en"]
let flatMap = country.flatMap { (value) in
    return value
}
print(flatMap) //["cn", "us", "en"]

// compactMap 過濾數組中的nil
let compactMap = country.compactMap { (value) in
    return value
}
print(compactMap) // ["cn", "us", "en"]
複製代碼

filter,reduce其實很好理解,map、flatMap、compactMap剛開始接觸時確實容易搞混,這個需要多加使用和練習。

注意到flatMap有兩種用法,一種是展開數組,將二維數組降爲一維數組,一種是過濾數組中的nil。在Swift4.1版本已經將flatMap過濾數組中nil的函數標位deprecated,所以我們過濾數組中nil的操作應該使用compactMap函數。

參考:Swift 燒腦體操(四) - map 和 flatMap

五、幾個Swift中的概念

柯里化什麼意思

柯里化指的是從一個多參數函數變成一連串單參數函數的變換,這是實現函數式編程的重要手段,舉個例子:

// 該函數返回類型爲(Int) -> Bool
func greaterThan(_ comparer: Int) -> (Int) -> Bool {
    return { number in
        return number > comparer
    }
}
// 定義一個greaterThan10的函數
let greaterThan10 = greaterThan(10)
greaterThan10(13)    // => true
greaterThan10(9)     // => false
複製代碼

所以柯里化也可以理解爲批量生成一系列相似的函數。

參考:柯里化 (CURRYING)

POPOOP的區別

OOP(object-oriented programming)面向對象編程:

在面向對象編程世界裏,一切皆爲對象,它的核心思想是繼承、封裝、多態。

POP(protocol-oriented programming)面向協議編程:

面向協議編程則主要通過協議,又或叫做接口對一系列操作進行定義。面向協議也有繼承封裝多態,只不過這些不是針對對象建立的。

爲什麼Swift演變成了一門面向協議的編程語言。這是因爲面向對象存在以下幾個問題:

1、動態派發的安全性(這應該是OC的困境,在Swift中Xcode是不可能讓這種問題編譯通過的)

2、橫切關注點(Cross-Cutting Concerns)問題。面向對象無法描述兩個不同事物具有某個相同特性這一點。

3、菱形問題(比如C++中)。C++可以多繼承,在多繼承中,兩個父類實現了相同的方法,子類無法確定繼承哪個父類的此方法,由於多繼承的拓撲結構是一個菱形,所以這個問題有被叫做菱形缺陷(Diamond Problem)。

參考文章:

Swift 中的面向協議編程:是否優於面向對象編程?

面向協議編程與 Cocoa 的邂逅 (上)

AnyAnyObject 區別

AnyObject: 是一個協議,所有class都遵守該協議,常用語跟OC對象的數據轉換。

Any:它可以代表任何型別的類(class)、結構體 (struct)、枚舉 (enum),包括函式和可選型,基本上可以說是任何東西。

rethrowsthrows 有什麼區別呢?

throws是處理錯誤用的,可以看一個往沙盒寫入文件的例子:

// 寫入的方法定義
public func write(to url: URL, options: Data.WritingOptions = []) throws
// 調用
do {
    let data = Data()
    try data.write(to: localUrl)
} catch let error {
    print(error.localizedDescription)
}
複製代碼

將一個會有錯誤拋出的函數末尾加上throws,則該方法調用時需要使用try語句進行調用,用於提示當前函數是有拋錯風險的,其中catch句柄是可以忽略的。

rethrowsthrows並沒有太多不同,它們都是標記了一個方法應該拋出錯誤。但是 rethrows 一般用在參數中含有可以 throws 的方法的高階函數中(想一下爲什麼是高階函數?下期給出答案)。

查看map的方法聲明,我們能同時看到 throws,rethrows

@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
複製代碼

不知道你們第一次見到map函數本體的時候會不會疑惑,爲什麼map裏的閉包需要拋出錯誤?爲什麼我們調用的時候並沒有用try語法也可以正常通過?

其實是這樣的,transform是需要我們定義的閉包,它有可能拋出異常,也可能不拋出異常。Swift作爲類型安全的語言就需要保證在有異常的時候需要使用try去調用,在沒有異常的時候要正常調用,那怎麼兼容這兩種情況呢,這就是rethrows的作用了。

func squareOf(x: Int) -> Int {return x * x}

func divideTenBy(x: Int) throws -> Double {
    guard x != 0 else {
        throw CalculationError.DivideByZero
    }
    return 10.0 / Double(x)
}

let theNumbers = [10, 20, 30]
let squareResult = theNumbers.map(squareOf(x:)) // [100, 400, 9000]

do {
    let divideResult = try theNumbers.map(divideTenBy(x:))
} catch let error {
    print(error)
}
複製代碼

當我們直接寫let divideResult = theNumbers.map(divideTenBy(x:))時,編譯器會報錯:Call can throw but is not marked with 'try'。這樣就實現了根據情況去決定是否需要用try-catch去捕獲map裏的異常了。

參考:錯誤和異常處理

break return continue fallthough 在語句中的含義(switch、while、for)

這個比較簡單,只說相對特別的示例吧,在Swift的switch語句,會在每個case結束的時候自動退出該switch判斷,如果我們想不退出,繼續進行下一個case的判斷,可以加上fallthough

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章