繼令你極度舒適的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"))
有值的多重可選類型aOptString
和literalOptString
是等效的。
var optNil: String? = nil
var aOptNil: String?? = optNil
var literalOptNil: String?? = nil
print(aOptNil) // Optional(nil)
print(literalOptNil) // nil
爲nil
的多重可選類型aOptNil
和literalOptNil
是不一樣的類型。這說明,多重可選類型的分層邏輯還是很嚴謹的,它能明確定位nil
究竟在哪一層。
class和 static的區別
有一個A類
,還有一個B類
繼承於A類
。下面代碼中介紹了,關於父類和子類中使用static
和class
關鍵字的場景。
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"
// }
}
在類中static
和class
關鍵字都可以修飾方法
和屬性,
但有一些本質的不同:
- 1.應用類型
static修飾,表示靜態方法或靜態屬性,可以用於所有類型 class,struct,enum。
class修飾,表示類方法或類屬性,只可以用於 class中。 - 2.屬性類型
static修飾的屬性可以是計算屬性也可以是存儲屬性。
class修飾的屬性只能是計算屬性。 - 3.繼承重寫
static修飾的類方法和屬性是可以繼承重寫的。
class修飾的類方法和類屬性無法在子類中重寫,相當於final class。
.Type、.self、Self
.Type
: 當前類的元類型(Meta)。
.self
: 靜態獲取當前類型或者實例的本身(包括class
、struct
、enum
、protocol
)。
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
是一個指定爲BaseClass
的SubClass
實例對象,這在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基礎知識碎片中也曾聊過。
觀察屬性
在類ObserverA
和ObserverB
中,觀察屬性的使用邏輯和重寫邏輯如下:
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
協議可以很輕易地完成JOSN
和model
之間的轉化。
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->companyName
、create_time->createTime
、worth->special
。
還可以通過設置key的策略
,系統自動轉化劃線轉駝峯。所以這個屬性設置後,可以省略company_name和create_time。當設置decoder
的keyDecodingStrategy
屬性爲.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
首先,這三者都是協議,它們之間的關係是,Hashable
和Comparable
都是繼承自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
如果你自定義的類型滿足下麪條件,編譯器會自動實現Hashable
和Equatable
,只需遵循即可。
- 在
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
表達式可以引用self
,KeyPath
實例指向當前實例自身。
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
需要依賴的東西比原來多,需要屬性有dynamic
和objc
進行修飾。
大多數情況下,我們想要觀察的類包含這兩個修飾,當然會損失一部分性能。並且有時候我們很可能也無法修改想要觀察的類的源碼,還需要繼承這個類並且將需要觀察的屬性使用 dynamic
和objc
進行重寫。
所以,在純Swift
中還是儘量Swift化,用屬性的set/get機制
來實現相同的效果,這纔是最佳方式。