SwiftTips之Language&API

令你極度舒適的Swift集合類高階函數之後,把很久之前Swift知識進行了梳理並總結成文。這些Swift知識點大多是一些細節,容易忽略但使用效果又極佳,其中包括語言基礎、內存、指針、OC差異、優雅奇點、開發環境等方面。其中包含一部分總結了喵神書中的點,特此註明。

if case let

首先,一個用到數據綁定的switch語法是這樣的。

let someTuple = (66, 99)
switch someTuple {
case (100, _):
    print("左側100, 不在乎右側")
case (66, let right):
    print("左側66, 右側\(right)")
case let (left, 88):
    print("左側\(left), 右側爲88")
case let (left, right):
    print("其它: \(left) + \(right)")
}

if case let其實和switch是有關係的,正如下面代碼中的,兩種表達式的效果是相同的。所以if case let其實就相當於switch case let的邏輯分支。

switch someTuple {
case let (left, 99):
    print("左側\(left), 右側99")
default:
    print("右側非99")
}

// 與上面的代碼效果一致
if case let (left, 99) = someTuple {
    print("左側\(left), 右側99")
}

除了if case let,類似的表達方式還有guard case let

guard case let (left, 99) = someTuple  else {
    fatalError("右側非99")
}

以及for case let

let tupleArray = [(11, 22), (33, 55), (77, 77), (88, 88)]

// 而下面兩種表達方式的效果是相同的
for (left, right) in tupleArray {
    print("\(left) + \(right)")
}
for case let (left, right) in tupleArray {
    print("\(left) + \(right)")
}

// 而且還可以配合 where使用
for case let (left, _) in tupleArray where left < 50 {
    print("左側\(left)小於50")
}

當然你可以說這種寫法很雞肋,因爲沒有必要多敲兩個代碼。但它的真正價值在於你使用有關聯值的枚舉時,這並不是元組可以代替的。

enum BirthDay {
    case time(year: Int, month: Int, day: Int)
}

let birth1 = BirthDay.time(year: 2000, month: 1, day: 2)
let birth2 = BirthDay.time(year: 2010, month: 5, day: 20)
let birth3 = BirthDay.time(year: 2020, month: 10, day: 30)
let birthArr = [birth1, birth2, birth3]

for case let BirthDay.time(_, month, day) in birthArr where month <= 6 {
    print("他是前半年的生日\(month)\(day)日")
}

== 和 ===

==表示值相同。值類型和引用類型都可以比較。

let string1 = "string"
let string2 = "string"

string1 == string2			// true

===表示兩個引用類型引用自相同的實例,即引用了同一塊內存區域。只能比較於引用類型。

let aView = UIView()
let bView = aView

aView == bView		// true
aView === bView		// true

多重可選類型

var optString: String? = "abc"
var aOptString: String?? = optString
var literalOptString: String?? = "abc"

print(aOptString)           // Optional(Optional("string"))
print(literalOptString)     // Optional(Optional("string"))

有值的多重可選類型aOptStringliteralOptString是等效的。

var optNil: String? = nil
var aOptNil: String?? = optNil
var literalOptNil: String?? = nil

print(aOptNil)          // Optional(nil)
print(literalOptNil)    // nil

nil的多重可選類型aOptNilliteralOptNil是不一樣的類型。這說明,多重可選類型的分層邏輯還是很嚴謹的,它能明確定位nil究竟在哪一層。

class和 static的區別

有一個A類,還有一個B類繼承於A類。下面代碼中介紹了,關於父類和子類中使用staticclass關鍵字的場景。

class A {
    // class修飾
    class func aClassMethod() {}

    class var aClassProperty: String {
        return "a"
    }

    //    class var aClassSaveProperty = ""
    // Error: Class stored properties not supported in classes; did you mean 'static'?


    // static修飾
    static func aStaticMethod() {}

    static var aStaticProperty: String {
        return "ap"
    }
}

class B: A {
    // class修飾
    override class func aClassMethod() {}

    override class var aClassProperty: String {
        return "b"
    }

    // static修飾
    // Error: Cannot override static method
    //    static func aStaticMethod() {}

    // Error: Cannot override static property
    //    override static var aStaticProperty: String {
    //        return "bp"
    //    }
}

在類中staticclass關鍵字都可以修飾方法屬性, 但有一些本質的不同:

  • 1.應用類型
    static修飾,表示靜態方法或靜態屬性,可以用於所有類型 class,struct,enum。
    class修飾,表示類方法或類屬性,只可以用於 class中。
  • 2.屬性類型
    static修飾的屬性可以是計算屬性也可以是存儲屬性。
    class修飾的屬性只能是計算屬性。
  • 3.繼承重寫
    static修飾的類方法和屬性是可以繼承重寫的。
    class修飾的類方法和類屬性無法在子類中重寫,相當於final class。

.Type、.self、Self

.Type: 當前類的元類型(Meta)。
.self: 靜態獲取當前類型或者實例的本身(包括classstructenumprotocol)。
Self: 不是一個特定的類型,遵循當前協議的類型或者當前類及其子類。

struct S {
    static var classProperty = ""
    var instanceProperty = ""
}
protocol P {}

.Type.self

我認爲下面的例子將.Type.self的區別表述的非常清楚了。

type(of: S())         // S

// S()實例, 其類型是 S
type(of: S().self)    // S

// S類型的本身, 其類型是 S.Type
type(of: S.self)      // S.Type

// S類型的元類, 其類型是 S.Type.Type
type(of: S.Type.self) // S.Type.Type

// P協議的本身, 其類型是 P.Protocol
type(of: P.self)      // P.Protocol

// P協議的元類, 其類型是 P.Type.Protocol
type(of: P.Type.self) // P.Type.Protocol

// intType的值是 Int, 類型是 Int.Type
let intType: Int.Type = Int.self
intType         // Int

在效果上,.self在類型後相當於取得類型本身,在實例後相當於取得這個實例本身。

S.classProperty
S().instanceProperty

在語法上,.self是可以省略不寫的,所以下面的代碼與上面的效果一致。

S.self.classProperty
S().self.instanceProperty

classForCoder也是一個獲取類型的方法,但最好不要使用。它是Foundation框架下的NSObject屬性,並不是swift的屬性。在Swift開發中,儘可能保持Swift化

UIImageView.classForCoder()			// 不推薦
UIImageView.self					// 推薦

Self

Swift不能在協議中定義泛型進行限制,所以在聲明或者實現協議時,Self就可以來代指實現這個協議本身的類型。

protocol SomeProcotol {
    func someFunc() -> Self
}

另外,用在類中,只可以用作方法的返回值(其他位置不可以使用)。
Self表示當前類及其子類,這裏僅限於 class。

class SS {
    func some() -> Self { return self }
}
SS().some()

swift中動態和靜態地獲取類型

type(of: someInstance): 動態獲取當前實例的類型.
.dynamicType: Deprecated, instead of type(of:)
someInstance.self: 靜態獲取類型
is: 靜態獲取類型

class BaseClass {
    class func printClassName() {
        print("BaseClass")
    }
}
class SubClass: BaseClass {
    override class func printClassName() {
        print("SubClass")
    }
}
let someInstance: BaseClass = SubClass()

someInstance是一個指定爲BaseClassSubClass實例對象,這在OC中稱爲多態。

Swift默認情況下是不採用動態派發,而是靜態的,所以函數的調用是在編譯時期決定。比如is條件語句、.self,都是靜態獲取類型的。

someInstance is SubClass     		// True
someInstance is BaseClass    		// True
BaseClass.self is BaseClass.Type	// True

獲取一個對象的動態類型,可以通過 type(of:)

type(of: someInstance) == SubClass.self      // True
type(of: someInstance) == BaseClass.self     // False

函數嵌套

這裏所說的函數嵌套與柯里化中所提到的函數分層調用不是一個概念,而是在函數中繼續定義函數。
來看下面一個函數:

func generateObjec(type: Int) -> String {
    if 0 == type {
        return zeroType()
    } else if 1 == type {
        return oneType()
    } else {
        return defaultType()
    }
}

func zeroType() -> String {
    return "Zero"
}

func oneType() -> String {
    return "One"
}

func defaultType() -> String {
    return "Two"
}

如果使用函數嵌套將會如下效果:

func generateObjec(type: Int) -> String {
    func zeroType() -> String {
        return "Zero"
    }
    func oneType() -> String {
        return "One"
    }
    func defaultType() -> String {
        return "Two"
    }
    
    if 0 == type {
        return zeroType()
    } else if 1 == type {
        return oneType()
    } else {
        return defaultType()
    }
}

函數嵌套在你函數主體內容過長,且本模塊的功能與外部邏輯沒有任何關係時,能發揮非常大的作用。它會使你的單個函數不在冗長,且將它們分成幾個小型的模塊,且定義在主函數之內,並不影響外部的關係。

所以這樣的訪問權限和這樣的模塊化會提高代碼可讀性和維護性。這個Tip在之前文章Swift基礎知識碎片中也曾聊過。

觀察屬性

在類ObserverAObserverB中,觀察屬性的使用邏輯和重寫邏輯如下:

class ObserverA {
    var number :Int {
        get {
            print("get")
            return 1
        }
        set {
            print("set")
        }
    }
}
class ObserverB: ObserverA {
    override var number: Int {
        willSet {
            print("willSet")
        }
        didSet {
            print("didSet")
        }
    }
}

let obseverB = ObserverB()
obseverB.number = 0
// 打印順序:
// get
// willSet
// set
// didSet

總結如下:

  • 1.初始化方法對屬性的設定,以及在 willSet和 didSet中對屬性的再次設定都不會再次觸發屬性觀察。
  • 2.在 swift中的計算屬性只是提供 set和 get兩種方法,當你添加 willSet及 didSet方法時會報錯。所以在同一個類型中,屬性觀察和計算屬性是不能同時共存的。但我們可以通過繼承重寫計算屬性來實現屬性觀察的目的。
  • 3.當觸發 didSet的時候,會自動觸發一次 get,這是因爲 didSet中會用到 oldValue,而這個值需要在整個 set動作之前進行獲取並存儲待用,否則將無法確保正確性。如果我們不實現 didSet的話,那次 get也不會觸發。

Protocol的調用邏輯

protocol AProtocol {
    func method1() -> String
}

extension AProtocol {
    func method1() -> String {
        return "在Protocol中的實現"
    }

    func method2() -> String {
        return "在Protocol中的實現"
    }
}

struct AStruct: AProtocol {}

struct BStruct: AProtocol {
    func method1() -> String {
        return "在實際類中的實現"
    }

    func method2() -> String {
        return "在實際類中的實現"
    }
}

協議方法調用者爲實際類型的情況

如果實際類型實現了協議,那麼實際類型中協議的實現將被調用。
如果實際類型中沒有實現協議,那麼協議擴展中的默認實現將被調用。

AStruct().method1()     // 在Protocol中的實現
BStruct().method1()     // 在實際類中的實現

協議方法調用者爲被推斷爲協議類型的情況:

如果方法在協議中進行了聲明,且類型中實現了協議,那麼類型中的實現將被調用。
如果方法沒有在協議中聲明,或者在類型中沒有實現,那麼協議擴展中的默認實現被調用。

BStruct().method1()     // 在實際類中的實現
BStruct().method2()     // 在實際類中的實現

let aProtocol = BStruct() as AProtocol
aProtocol.method1()     // 在實際類中的實現
aProtocol.method2()     // 在Protocol中的實現

Objective-C協議的默認實現

Swift可以在擴展中實現協議,從而進行默認調用,而OC中並沒有這種方法。所以,孫源曾經封裝了一個庫來實現類似功能:ProtocolKit

下標語法

struct House {
    var peoples: [String]

    subscript(index: Int) -> String {
        set {
            peoples[index] = newValue
        }
        get {
            return peoples[index]
        }
    }

    subscript(people: String) -> Int? {
        return peoples.firstIndex(of: people)
    }
}

let peoples = ["Jordan", "Duncan", "YaoMing", "James", "Wade"]
var nbaHouse = House(peoples: peoples)
nbaHouse[1]			// "Duncan"
nbaHouse["James"]	// 3

nbaHouse[2]				// "YaoMing"
nbaHouse[2] = "Iverson"	// "Iverson"
nbaHouse.peoples		// ["Jordan", "Duncan", "Iverson", "James", "Wade"]

減少容器類的類型損失

我們想要不同類型的元素放入一個容器中,比如定義容器中元素類型 Any或者 Anyobject,但這樣的轉換會造成部分信息的損失。

let mixed: [Any] = [1, "two", true]
let any = mixed[0]

我們想要放入一個容器中的元素或多或少會有某些共同點,這就使得用協議來規定。這種方法雖然也損失了一部分類型信息,但是相對於Any或者Anyobject還是改善很多。

let mixed2: [CustomStringConvertible] = [1, "two", true]
for obj in mixed2 {
    print(obj.description)
}

另一種做法是使用enum可以嵌套值的特點,將相關信息封裝進enum中。這個方法絕對無懈可擊。

enum MixedWrap {
    case IntValue(Int)
    case StringValue(String)
    case BoolValue(Bool)
}

let mixed3 = [MixedWrap.IntValue(1),
              MixedWrap.StringValue("two"),
              MixedWrap.BoolValue(true)]

for value in mixed3 {
    switch value {
    case let .IntValue(i):
        print(i)
    case let .StringValue(s):
        print(s)
    case let .BoolValue(b):
        print(b)
    }
}

模式匹配

在 Swift 中,使用 ~= 來表示模式匹配的運算符。~=操作符有下面三種API:

  • 1.判等類型是否相同
    func ~=(a: T, b: T) -> Bool
  • 2.判等與 nil比較的類型
    func ~=(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
  • 3.判等一個範圍輸入和某個特定值
    func ~=(pattern: I, value: I.Bound) -> Bool

Swift的switch就是使用了~=運算符進行模式匹配,case指定的模式作爲左參數輸入,而等待匹配的被switch的元素作爲運算符的右側參數。只不過這個調用是由 Swift隱式完成的。

switch "snail" {
case "snail":
    print("相等")
default:
    break
}

let num: Int? = nil
switch num {
case nil:
    print("nil")
default:
    print("\(num!)")
}

let x = 5
switch x {
case 0...10:
    print("Bound之內")
default:
    print("Bound之外")
}

再來看我們如何進行自定義switch的模式匹配。

func ~=(left: String, right: String) -> Bool {
    // 自定義爲包含條件
    return right.contains(left)
}

let content = "1234567890"
switch content {
case "123":
    print("包含數字")
case "abc":
    print("包含字母")
default:
    print("不包含")
}

// 包含數字

字面量語法

Swift 有一組非常方便的接口,用來將字面量轉換爲特定的類型。

  • ExpressibleByArrayLiteral
  • ExpressibleByBooleanLiteral
  • ExpressibleByDictionaryLiteral
  • ExpressibleByFloatLiteral
  • ExpressibleByIntegerLiteral
  • ExpressibleByStringLiteral
  • ExpressibleByUnicodeScalarLiteral
  • ExpressibleByExtendedGraphemeClusterLiteral

Int型的字面量語法

/**
 public protocol ExpressibleByIntegerLiteral {
 associatedtype IntegerLiteralType : _ExpressibleByBuiltinIntegerLiteral
 init(integerLiteral value: Self.IntegerLiteralType)
 }
*/

ExpressibleByIntegerLiteral是關於Int型的字面量語法的協議。

struct AgeStage: ExpressibleByIntegerLiteral {
    let age: Int

    init(age: Int) {
        self.age = age
    }

    init(integerLiteral value: Int) {
        self.init(age: value)
    }
}

let age18: AgeStage = 18
age18.age		// 18

字符串類型的字面量語法

/**
 public protocol ExpressibleByStringLiteral : ExpressibleByExtendedGraphemeClusterLiteral {
 associatedtype StringLiteralType : _ExpressibleByBuiltinStringLiteral
 init(stringLiteral value: Self.StringLiteralType)
 }
 */

ExpressibleByStringLiteral是關於字符串類型的字面量語法的協議。但因爲ExpressibleByStringLiteral是有繼承的關係的,所以其實需要遵循三個協議內容來實現字面量語法。

class Cow: ExpressibleByStringLiteral {
    let name: String
    init(name value: String) {
        self.name = value
    }

    required convenience init(stringLiteral value: String) {
        self.init(name: value)
    }

    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
    }

    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
    }
}

let cow: Cow = "Mar~Mar~"
cow.name    	// Mar~Mar~

自定義高級運算符

Seat類自定義運算符++++*,來提升代碼的簡潔度。併爲它們設定了運算的優先級,來確保運算過程的正確性。

precedencegroup MulAddPrecedence {
    associativity: none
    higherThan: MultiplicationPrecedence
}

infix operator +: AdditionPrecedence
infix operator ++: MultiplicationPrecedence
infix operator +*: MulAddPrecedence

struct Seat {
    var row = 0
    var column = 0

    static func + (left: Seat, right: Seat) -> Seat {
        let row = left.row + right.row
        let column = left.column + right.column
        return Seat(row: row, column: column)
    }

    static func ++ (left: Seat, right: Seat) -> Seat {
        let row = left.row + right.row * 2
        let column = left.column + right.column * 2
        return Seat(row: row, column: column)
    }

    static func +* (left: Seat, right: Seat) -> Seat {
        let row = left.row * left.row + right.row * right.row
        let column = left.column * left.column + right.column * right.column
        return Seat(row: row, column: column)
    }
}

let seat1 = Seat(row: 2, column: 3)
let seat2 = Seat(row: 4, column: 7)
let seat3 = Seat(row: 5, column: 10)

seat1 + seat2 + seat3		// row 11, column 20
seat1 ++ seat2 ++ seat3		// row 20, column 37
seat1 +* (seat2 +* seat3)	// row 1685, column 22210
//seat1 +* seat2 +* seat3	// Error

seat1 +* seat2 +* seat3這行代碼,會報錯:Adjacent operators are in non-associative precedence group ‘MulAddPrecedence’。這是由於其優先級MulAddPrecedence的結合性爲none,即沒有定義。這時候需要將其改爲left或者right

另外,關於運算符和優先級的一些說明可以看這裏,precedenceGroup、precedence、associativity

結合運算符封裝正則匹配工具

在Swift中沒有正則表達式的API,但可以使用OC中的 NSRegularExpression配合Swift中的特性來使用。下面代碼中封裝了一個正則工具:

struct RegexHelper {
    let regex: NSRegularExpression

    init(_ pattern: String) throws {
        try regex = NSRegularExpression(pattern: pattern, options: .caseInsensitive)
    }

    func match(_ content: String) -> Bool {
        let matches = regex.matches(in: content,
                                    options: [],
                                    range: NSMakeRange(0, content.utf16.count))
        return !matches.isEmpty
    }
}

let zhPattern = "^[\u{4e00}-\u{9fa5}]{0,}$"
let matcher = try RegexHelper(zhPattern)

if matcher.match("哈哈哈哈哈哈") {
    print("純中文字符串通過")
}

//純中文字符串通過

再進一步封裝,自定義一個運算符結合起來,更加簡便。

precedencegroup MatchPrecedence {
    associativity: none
    higherThan: DefaultPrecedence
}

infix operator =~: MatchPrecedence

func >> (pattern: String, content: String) -> Bool {
    do {
        return try RegexHelper(pattern).match(content)
    } catch _ {
        return false
    }
}

if zhPattern >> "DCSnail哈哈哈" {
    print("純中文字符串通過")
}
//

集合協議Sequence和IteratorProtocol

想修改或者實現一個自定義的集合類型,就需要用到Sequence協議,以及IteratorProtocol協議。它們的API如下:

/**
 public protocol IteratorProtocol {
 associatedtype Element
 
 mutating func next() -> Self.Element?
 }
 public protocol Sequence {
 associatedtype Element where Self.Element == Self.Iterator.Element
 associatedtype Iterator : IteratorProtocol
 
 __consuming func makeIterator() -> Self.Iterator
 var underestimatedCount: Int { get }
 func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Self.Element>) throws -> R) rethrows -> R?
 }
 */

比如要自定義一個反向迭代的數組,需要編寫一個反向的迭代器,並植入Sequence協議中。

struct CustomReverseIterator<T>: IteratorProtocol {

    typealias Element = T
    var array: [Element]
    var currentIndex: Int

    init(_ array: [Element]) {
        self.array = array
        currentIndex = array.count - 1
    }

    mutating func next() -> Element? {
        guard currentIndex >= 0 else {
            return nil
        }
        let int = array[currentIndex]
        currentIndex -= 1
        return int
    }
}

struct CustomReverseSequence<T>: Sequence {

    var array: [T]
    init(_ array: [T]) {
        self.array = array
    }

    typealias intrator = CustomReverseIterator<T>
    func makeIterator() -> intrator {
        return CustomReverseIterator(self.array)
    }
}


let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
let sequence = CustomReverseSequence(animals)
for animal in sequence {
    print(animal)
}

// Dolphin
// Camel
// Butterfly
// Antelope

Codable

Codable是Swift中關於序列化反序列化的標準協議。在官方API中Decodable
協議和Encodable協議的別名稱作Codable

 public protocol Decodable {
 init(from decoder: Decoder) throws
 }
 public protocol Encodable {
 func encode(to encoder: Encoder) throws
 }
 public typealias Codable = Decodable & Encodable

JSON數據如下,數據模型對應的類型爲定義爲Fish,在Swift中通過Codble協議可以很輕易地完成JOSNmodel之間的轉化。

let fishJson = """
    [{
        "name": "SmallFish",
        "age": 1,
    },{
        "name": "BigFish",
        "age": 3,
    }]
    """.data(using: .utf8)!

struct Fish: Codable {
    var name = ""
    var age = 0
}

Encodable、Decodable

Decodable:JSON -> 模型

let decoder = JSONDecoder()
do {
    // 字典使用Type.self; 數組使用[Type].self
    let fishs = try decoder.decode([Fish].self, from: fishJson)
    print(fishs)
    // [__lldb_expr_1.Fish(name: "SmallFish", age: 1), __lldb_expr_1.Fish(name: "BigFish", age: 3)]
} catch {
    print(error)
}

Encodable:模型 -> JSON

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
    let data = try encoder.encode(Fish(name: "OldFish", age: 5))
    print(String(data: data, encoding: .utf8)!)
    // { "name" : "OldFish", "age" : 5 }
} catch {
    print(error)
}

進入正題,在如上代碼中Fish類僅僅寫了一個遵循Codable的代碼,Swift是如何進行轉化的呢?其實,結構體Fish類遵循Codable的代碼後,會自動實現以下內容:

 init(name: String, age: Int) {
 self.name = name
 self.age = age
 }

 // Decodable
 init(from decoder: Decoder) throws {
 let container = try decoder.container(keyedBy: CodingKeys.self)
 let name = try container.decode(String.self, forKey: .name)
 let age = try container.decode(Int.self, forKey: .age)
 self.init(name: name, age: age)
 }

 // Encodable
 func encode(to encoder: Encoder) throws {
 var container = encoder.container(keyedBy: CodingKeys.self)
 try container.encode(name, forKey: .name)
 try container.encode(age, forKey: .age)
 }

 enum CodingKeys: String, CodingKey {
 	case name
 	case age
 }

CodingKeys

當然在實際開發中,沒有這麼簡單,可能還有數據模型中key的轉化、value的轉化等。下面中舉例了companyJson和接收類型Company的關係。

let companyJson = """
        {
            "company_name": "****科技有限公司",
            "create_time": 1556676900,
            "state": "suspend",
            "worth": "---",
            "employees": [{
                "name": "SmallFish",
                "age": 1,
            },{
                "name": "BigFish",
                "age": 3,
            }],
        }
        """.data(using: .utf8)!


struct Company: Codable {
    var companyName: String
    var createTime: Date
    var state: CompanyState
    var special: Float
    var employees: [Fish]

    enum CodingKeys: String, CodingKey {
        case companyName = "company_name"
        case createTime = "create_time"
        case state
        case special = "worth"
        case employees
    }

    enum CompanyState: String, Codable {
        case open = "open"
        case close
        case suspend
    }
}

key的轉化:通過對 CodingKeys枚舉進行自定義,來實現 key的對應轉化。上面代碼中自定義的轉化爲company_name->companyNamecreate_time->createTimeworth->special

還可以通過設置key的策略,系統自動轉化劃線轉駝峯。所以這個屬性設置後,可以省略company_name和create_time。當設置decoderkeyDecodingStrategy屬性爲.convertFromSnakeCase後就會自動轉化爲companyName和createTime了。

value的轉化:通過將state定義爲關聯值爲字符串的枚舉類型CompanyState,以實現value從字符串向枚舉的自主轉化,以及整體的Codable。這裏極大的體現了Swift中枚舉的靈活性,利用其關聯值的特性進行自主轉化。

但是比如,json中的某個value爲字符串類型,想要在model中轉化爲Int類型,這就需要自己實現Codable的協議了,自己定義轉化了。當然你實現後,也會覆蓋系統的自動實現。

// date的轉化策略, 由時間戳直接轉化爲 date
decoder.dateDecodingStrategy = .secondsSince1970
// 其它的轉化策略...
// special指定 infinity、-infinity、nan 三個特殊值的轉化
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+++", negativeInfinity: "---", nan: "***")

do {
    let company = try decoder.decode(Company.self, from: companyJson)
    print(company)
    // Company(companyName: "****科技有限公司", createTime: 2019-05-01 02:15:00 +0000, state: __lldb_expr_1.Company.CompanyState.suspend, special: -inf, employees: [__lldb_expr_1.Fish(name: "SmallFish", age: 1), __lldb_expr_1.Fish(name: "BigFish", age: 3)])
} catch {
    print(error)
}

補充:NaN是Not a Number的簡寫,可以用來表示某些未被定義的或者出現了錯誤的運算。

let a = 0.0 / 0.0					// nan
let b = sqrt(-1.0)					// nan
let c = 0.0 * Double.infinity	// nan
a.isNaN								// true

Equatable、Hashable、Comparable

Equatable

首先,這三者都是協議,它們之間的關係是,HashableComparable都是繼承自Equatable協議。

實現Equatable協議後,就可以用==符號來判斷兩個對象是否相等了。

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

這裏先不用代碼說明,因爲下面Hashable協議中會提到。

Hashable

遵循Hashable協議,也必須滿足Equatable協議。因爲Hashable協議繼承自Equatable協議。實現了Hashable協議就可以使用哈希值了。

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

官方文檔中指出,hashValue is deprecated as a Hashable requirement.To conform to Hashable,implement the hash(into:) requirement instead。如今Hashable協議,已不再要求必須實現hashValue了,由hash(into:)代替。

對於class來說,Hashable協議實現是這樣的:

class Duck: Hashable {
    let name: String
    let age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // Hashable
    func hash(into hasher: inout Hasher) {
        // 需要注意的是不同的類比較哈希的話, combine的順序需要保證一致
        hasher.combine(name)
        hasher.combine(age)
    }

    // hashValue 已不再要求必須實現了, 由上面hash(into:)代替
    //    var hashValue: Int {
    //        return self.name.hashValue ^ self.age.hashValue
    //    }

    // Equatable
    static func == (lhs: Duck, rhs: Duck) -> Bool {
        if lhs.name == rhs.name && lhs.age == rhs.age{
            return true
        }
        return false
    }
}

let duck = Duck(name: "Duck", age: 2)
let duck2 = Duck(name: "Duck", age: 2)
// 哈希值
duck.hashValue		// -7196963873420679324
duck2.hashValue		// -7196963873420679324
// 手動實現的Equatable
duck == duck2		// true

如果你自定義的類型滿足下麪條件,編譯器會自動實現HashableEquatable,只需遵循即可。

  • struct中,存儲屬性如果都遵循Hashable協議。
  • enum中,關聯值如果都遵循Hashable協議。

但是對於class來說是不會自動合成的,需要手動實現.

struct Animal: Hashable {
    let name: String
    let age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let animal = Animal(name: "Animal", age: 3)
let animal2 = Animal(name: "Animal", age: 3)
// 哈希值
animal.hashValue	// 8606943294433516990
animal2.hashValue	// 8606943294433516990
// 自動實現的Equatable
animal == animal2	// true

關於哈希值,需要說明的是,除非我們正在開發一個哈希散列的數據結構,否則我們不應該直接依賴系統所實現的哈希值來做其他操作。

首先哈希的定義是單向的,對於相等的對象或值,我們可以期待它們擁有相同的哈希,但是反過來並不一定成立。其次,某些對象的哈希值有可能隨着系統環境或者時間的變化而改變。

因此你也不應該依賴於哈希值來構建一些需要確定對象唯一性的功能,在絕大部分情況下,你將會得到錯誤的結果。

Comparable

遵循Comparable協議,當然也需要滿足Equatable協議。實現對應的協議方法後就可以使用<<=>=>等符號進行比較了。

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
}

當爲Tiger自定義比較條件後,就會感到Swift這些協議真是太便捷了。

struct Tiger: Comparable {

    let name: String
    let age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // Comparable
    static func < (lhs: Tiger, rhs: Tiger) -> Bool {
        if lhs.age < rhs.age {
            return true
        }
        return false
    }

    static func > (lhs: Tiger, rhs: Tiger) -> Bool {
        if lhs.age > rhs.age {
            return true
        }
        return false
    }

    // Equatable
    static func == (lhs: Tiger, rhs: Tiger) -> Bool {
        if lhs.age == rhs.age{
            return true
        }
        return false
    }
}

let tiger = Tiger(name: "Tiger", age: 4)
let tiger2 = Tiger(name: "Tiger", age: 5)
tiger > tiger2		// false

KeyPath

Key-Path表達式

\TypeName.path

這個Key-Path表達式會在編譯期生成一個KeyPath類的實例。而這個KeyPath實例指向某個類型的屬性或者下標,KeyPath類可配合下標subscript(keyPath:)進行使用。

不明白?上代碼。看看KeyPath類是如何指向一個類型的屬性的。

struct Person {
    var name: String
    var dog: [Dog]
}

struct Dog {
    var name: String
}

var titi = Dog(name: "Titi")
var james = Person(name: "James", dog: [titi])


// 創建 KeyPath類的實例
let nameKeyPath = \Person.name
james[keyPath: nameKeyPath] = "James Wade"

// 可省略類型自主推斷
james[keyPath: \.name]	// James Wade

通過KeyPath類也可以實現存取屬性是數組的情況,需要注意的是Key-Path表達式中所使用的的下標必須滿足Hashable協議標準。另外,還支持字典類型。

james.dog[keyPath: \[Dog].[0].name] = "Titi Go"
james.dog		// Titi Go

let greetings = ["hello", "hola", "bonjour", "안녕"]
greetings[keyPath: \[String].[1]]		// hola

var keyIntDic = ["first": [1, 2, 3], "second": [4, 5, 6]]
keyIntDic[keyPath: \[String: [Int]].["first"]] = [0, 0, 0]
keyIntDic		// ["second": [4, 5, 6], "first": [0, 0, 0]]

Key-Path表達式可以引用selfKeyPath實例指向當前實例自身。

var compoundValue = (a: 1, b: 2)
compoundValue[keyPath: \.self] = (a: 10, b: 20)
compoundValue		// (a 10, b 20)

Key-Path字符串表達式

#keyPath(TypeName.property)

這個Key-Path字符串表達式會在編譯期轉化爲字符串字面量並生成一個字符串。Key-Path字符串表達式可配合Objective-C中的setValue:forKey:進行使用。

class Cat: NSObject {
    @objc var age: Int

    init(age: Int) {
        self.age = age
    }

    func agekeyPath() -> String {
        return #keyPath(age)
    }
}

let kitty = Cat(age: 2)

// 在編譯時,被字符串字面量所取代
let ageKeyPath = #keyPath(Cat.age)
kitty.setValue(3, forKey: ageKeyPath)
kitty.value(forKey: ageKeyPath)

// 只有在類的內部key-Path字符串表達式纔可以省略類名, 其它情況都不可省略
kitty.value(forKey: kitty.agekeyPath())

因爲需要支持Objective-C中setValue:forKey:,所以屬性必須使用@objc修飾。又因爲屬性需要@objc修飾,所以當前類型必須是class,且必須繼承於任何一個OC類。所以,Key-Path字符串表達式還是偏OC的,我還是那句話,儘量做到Swift化。

KVO

KVO在Swift中當然還是有的,對於繼承自NSObjc的類,可以隨意使用KVO。使用的API也很簡單:

var view = UIView()
let observer: NSKeyValueObservation = view.observe(\.frame, options: [.new]) { v, changed in
    print(v)
    print(changed.newValue)
}
view.frame = CGRect(x: 0, y: 0, width: 10, height: 10)

// <UIView: 0x7f9660d08c90; frame = (0 0; 10 10); layer = <CALayer: 0x6000035da920>>
// Optional((0.0, 0.0, 10.0, 10.0))

但對於非NSObject的類就需要@objc dynamic來修飾之後,才能使用KVO。因爲這是屬於OC的東西。

class Panda: NSObject {
    @objc dynamic var name = ""
}

var panda = Panda()
panda.observe(\.name, options: [.new]) { (pa, changed) in
    print(pa)
    print(changed.newValue)
}
panda.name = "panda"
// Optional("panda")

Swift中的KVO需要依賴的東西比原來多,需要屬性有dynamicobjc進行修飾。
大多數情況下,我們想要觀察的類包含這兩個修飾,當然會損失一部分性能。並且有時候我們很可能也無法修改想要觀察的類的源碼,還需要繼承這個類並且將需要觀察的屬性使用 dynamicobjc進行重寫。

所以,在純Swift中還是儘量Swift化,用屬性的set/get機制來實現相同的效果,這纔是最佳方式。

相關資料

Swift基礎知識碎片
令你極度舒適的Swift集合類高階函數
Swift關鍵字總結

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