Swift黃金選手鑑定手冊

image-20200511230812677

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

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

1、 協議 Protocol

ExpressibleByDictionaryLiteral

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

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)swift

舉個例子,構造字典咱們能夠經過如下兩種方式進行:api

// 方法一:
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遵循的協議關係圖,有助於你們理解上面講解的幾個協議之間的關係:

img

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

2、@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)
        }
    }
}

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

複製代碼

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

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

參考:Swift Property Wrappers

3、關鍵字

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'。固然咱們仍是應該理解二者的區別。

4、高階函數

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

5、幾個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演變成了一門面向協議的編程語言。這是由於面向對象存在如下幾個問題:

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

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

三、菱形問題(好比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

相關文章
相關標籤/搜索