上一節(jié),我們分析了閉包 & Runtime & Any等類(lèi)型。
介紹Runtime運(yùn)行時(shí)
時(shí),我們知道swift
是靜態(tài)語(yǔ)言
,但可兼容OC類(lèi)
實(shí)現(xiàn)objc_msgSend
消息發(fā)送機(jī)制。
-
swift
中dynamic
聲明的函數(shù)可在extension
中進(jìn)行函數(shù)替換
。不過(guò)這個(gè)替換
在編譯期
就完成
了。
那,swift
有沒(méi)有自己的運(yùn)行時(shí)機(jī)制
呢?
- 有,
Mirror
反射機(jī)制。雖然沒(méi)
有OC運(yùn)行時(shí)
那么強(qiáng)大
。但支持運(yùn)行時(shí)
獲取對(duì)象
的類(lèi)型
和成員變量
(HandyJSON
就是基于Mirror思想
,直接從內(nèi)存讀取
來(lái)實(shí)現(xiàn)的??)
本節(jié),我們先體驗(yàn)一下Mirror
,下一節(jié),我們深入研究Mirror底層原理
- Mirror體驗(yàn)
- protocol協(xié)議
- Error錯(cuò)誤機(jī)制(try、throws、rethrows)
- defer
- assert
1. Mirror體驗(yàn)
-
Mirror
(反射),可以動(dòng)態(tài)
獲取類(lèi)型
、成員變量
,在運(yùn)行時(shí)
可以調(diào)用方法
、屬性
等行為的特性
。
對(duì)于一個(gè)純swift類(lèi)
來(lái)說(shuō),并不支持
我們直接像OCRuntime
那樣操作。但Swift標(biāo)準(zhǔn)庫(kù)
給我們提供
了簡(jiǎn)單實(shí)用
的反射機(jī)制
。
- 測(cè)試案例:
class HTPerson { var name = "ht" var age = 18 } let p = HTPerson() let mirror = Mirror(reflecting: p.self) for pro in mirror.children { print("\(pro.label ?? ""):\(pro.value)") }
- 打印結(jié)果:
image.png
- 進(jìn)入
Mirror內(nèi)部
可以看到:
Mirror是一個(gè)
struct
結(jié)構(gòu)體
image.pngMirror 初始化時(shí),入?yún)?code>Any類(lèi)型
image.png
children
屬性為(label: String?, value: Any)
元組類(lèi)型
image.png
-
Mirror
的用途很多
,最常用的是用于JSON解析
:
class HTPerson {
var name = "ht"
var age = 18
var student = HTStudent() // 持有對(duì)象
}
class HTStudent {
var double = 10.0
}
func test(_ obj: Any) -> Any{
let mirror = Mirror(reflecting: obj)
// 遞歸終止條件
guard !mirror.children.isEmpty else { return obj }
var keyValue: [String: Any] = [:] // 記錄屬性名和屬性?xún)?nèi)容
for children in mirror.children{
if let keyName = children.label {
keyValue[keyName] = test(children.value) // 遞歸(繼續(xù)遍歷person中的對(duì)象屬性)
}else{
print("children.label ")
}
}
return keyValue
}
let t = HTPerson()
print(test(t))
-
打印結(jié)果:
image.png
分析:
mirror
成功讀取對(duì)象
的屬性名
和屬性值
,可使用字典
存儲(chǔ)和輸出;每個(gè)
屬性
都會(huì)默認(rèn)
當(dāng)作對(duì)象
來(lái)處理,通過(guò)mirror.children
可判斷當(dāng)前對(duì)象
是否擁有屬性
,沒(méi)有就跳出當(dāng)前屬性
的遞歸
流程。 這樣可保證
所有讀取children
的完整層級(jí)信息
。(ps:
遞歸
實(shí)際是利用棧
特性,直接
或間接
調(diào)用自身
實(shí)現(xiàn)層級(jí)讀取
的需求
,遞歸需要
有明確的終止條件
)
- 上面代碼有個(gè)
不好
的點(diǎn):錯(cuò)誤結(jié)果
是print
打印,外界
并不知道
。
在swift
中,錯(cuò)誤信息
一般使用Error
進(jìn)行表示。
2. Protocol協(xié)議
在介紹
Error
之前,我們先介紹一下Swift
的協(xié)議
。
Swift
的協(xié)議
非常強(qiáng)大
,不僅可以聲明屬性
、函數(shù)
,支持可選實(shí)現(xiàn)
,還可以在extension
中完成屬性
和函數(shù)
的默認(rèn)實(shí)現(xiàn)
。以上面案例為例:
如果每次
讀取屬性
,我們都需要調(diào)用test函數(shù)
,會(huì)顯得非常麻煩
。
協(xié)議
可以幫我們默認(rèn)實(shí)現(xiàn)
這個(gè)方法
,只要對(duì)象遵守
這個(gè)協(xié)議
,都可以直接使用
。
【思路】
- 創(chuàng)建一個(gè)協(xié)議(
CustomJSONMap
),聲明jsonMap
函數(shù),并在extension
中默認(rèn)實(shí)現(xiàn)jsonMap
。- 創(chuàng)建一個(gè)枚舉
JSONMapError
,對(duì)所有錯(cuò)誤類(lèi)型
進(jìn)行列舉
。jsonMap
內(nèi)部支持Mirror
所有遵循CustomJSONMap
協(xié)議的屬性。對(duì)未遵循協(xié)議
的屬性和屬性名為空
的屬性返回
相應(yīng)的錯(cuò)誤類(lèi)型
。
// 錯(cuò)誤枚舉
enum JSONMapError {
case emptyError // 空
case notConformProtocol // 未遵守協(xié)議
}
// 協(xié)議(自帶jsonMap函數(shù))
protocol CustomJSONMap {
func jsonMap() ->Any
}
// extension中默認(rèn)jsonMap實(shí)現(xiàn)
extension CustomJSONMap {
func jsonMap() -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else { return self }
var keyValue: [String: Any] = [:]
for children in mirror.children{
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = value.jsonMap() // 遞歸
} else {
return JSONMapError.emptyError // 屬性名為空
}
} else {
return JSONMapError.notConformProtocol // children未遵守CustomJSONMap協(xié)議
}
}
return keyValue
}
}
class HTPerson: CustomJSONMap {
var name = "ht"
var age = 18
}
let t = HTPerson()
print(t.jsonMap())
-
運(yùn)行上述案例,可以看到打印了
notConformProtocol
因?yàn)?code>name和age
屬性分別是String
和Int
類(lèi)型,他們沒(méi)有遵守
CustomJSONMap協(xié)議。
image.png 既然如此,我們讓
String
和Int
都遵守CustomJSONMap
協(xié)議,再運(yùn)行。
extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}
-
完美打印
所有內(nèi)容
在我們的開(kāi)發(fā)中,
protocol協(xié)議
是一大利器
,使用非常方便。
- 遇到一些
通用
的函數(shù)
,我們可以使用協(xié)議包裝
起來(lái),只要遵循
這個(gè)協(xié)議
,就可以直接調(diào)用
相應(yīng)的屬性
和函數(shù)
。很好的隔離代碼
,而且讓代碼
看起來(lái)非常簡(jiǎn)潔
。
- 上面的
錯(cuò)誤信息
,我們只是使用枚舉
統(tǒng)一羅列
,return時(shí)
,常規(guī)結(jié)果
和錯(cuò)誤類(lèi)型
混在一起返回,讓我們很不舒服
。
如何解決?
- 可以通過(guò)系統(tǒng)
Error
協(xié)議搭配throw
關(guān)鍵字,優(yōu)雅的拋出錯(cuò)誤
或返回常規(guī)結(jié)果
,讓開(kāi)發(fā)者
自己選擇處理
方式。
3. Error機(jī)制(try、throws、rethrows)
swift
中,系統(tǒng)提供Error
協(xié)議來(lái)表示當(dāng)前應(yīng)用程序
發(fā)生錯(cuò)誤的情況
,并支持使用throw
關(guān)鍵字,優(yōu)雅
的拋出錯(cuò)誤
。
3.1 基礎(chǔ)Error協(xié)議
public protocol Error { }
- 系統(tǒng)的
Error協(xié)議
,就是一個(gè)空的公共協(xié)議
, 不管是class
、struct
還是enum
,都可以通過(guò)遵守
這個(gè)協(xié)議
,來(lái)表達(dá)錯(cuò)誤
。
以 enum
為例:
// 錯(cuò)誤枚舉,遵循Error
enum JSONMapError: Error {
case emptyError // 空
case notConformProtocol // 未遵守協(xié)議
}
// 協(xié)議(自帶jsonMap函數(shù))
protocol CustomJSONMap {
func jsonMap() throws ->Any
}
// extension中默認(rèn)jsonMap實(shí)現(xiàn)
extension CustomJSONMap {
func jsonMap() throws -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else { return self }
var keyValue: [String: Any] = [:]
for children in mirror.children{
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = try value.jsonMap() // 加了throws后,需要使用try執(zhí)行 jsonmap()
} else {
throw JSONMapError.emptyError // 屬性名為空
}
} else {
throw JSONMapError.notConformProtocol // children未遵守CustomJSONMap協(xié)議
}
}
return keyValue
}
}
class HTPerson: CustomJSONMap {
var name = "ht" // String未繼承CustomJSONMap,會(huì)error
var age = 18 // Int未繼承CustomJSONMap,會(huì)error
}
let t = HTPerson()
// do...catch 分開(kāi)處理,catch中有默認(rèn)參數(shù)error。
do {
// 處理正常結(jié)果
print(try t.jsonMap())
} catch {
// 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型)
print(error)
}
//print(try t.jsonMap()) // try : 向上甩鍋,將錯(cuò)誤拋給上層函數(shù)處理(上層沒(méi)處理,就crash)
print(try? t.jsonMap()) // try?: 只關(guān)注正常結(jié)果,忽略錯(cuò)誤(如果錯(cuò)誤,就為nil, 不執(zhí)行后續(xù)流程)
//print(try! t.jsonMap()) // try!: 對(duì)代碼絕對(duì)自信,一定不會(huì)發(fā)生錯(cuò)誤(如果發(fā)生,直接crash)
分析:
- 讓
JSONMapError
枚舉遵守Error
協(xié)議- 將
jsonMap
函數(shù)的return 錯(cuò)誤
,全部改為throw
拋出錯(cuò)誤,只有正常結(jié)果
才使用return
返回。- 使用
throw
拋出錯(cuò)誤會(huì)發(fā)現(xiàn),函數(shù)需要使用throws
標(biāo)注,這是告訴使用者
,這個(gè)函數(shù)可能拋出error
,需要開(kāi)發(fā)者
決定是否處理。- 使用
throw
后,發(fā)現(xiàn)不能直接調(diào)用jsonMap
,需要使用try
關(guān)鍵字來(lái)嘗試調(diào)用。
- 那
try
什么意思呢?如何使用
呢?
try有三種使用方式:
- try : 向上甩鍋,將
錯(cuò)誤
拋給上層
函數(shù)處理
(最上層都沒(méi)處理
,就crash
)- try?: 只關(guān)注
正常結(jié)果
,忽略
所有錯(cuò)誤
(如果錯(cuò)誤,就為nil, 不執(zhí)行后續(xù)流程)- try!: 程序員對(duì)代碼
絕對(duì)自信
,一定不會(huì)
發(fā)生錯(cuò)誤
(如果發(fā)生,直接crash
)
- 上面三種方式,只是說(shuō)到了
拋出
和不管
,那如何正確處理
throw拋出的錯(cuò)誤
呢?
do...catch:
- 我們可以通過(guò)
do...catch
來(lái)處理。其中do
處理正確結(jié)果
,catch
處理error
,catch有個(gè)隱藏參數(shù)
,就是error
(Error類(lèi)型)do { // 處理正常結(jié)果 try t.jsonMap() // 這里try不會(huì)報(bào)錯(cuò)了,因?yàn)殄e(cuò)誤都分給了catch } catch { // 處理錯(cuò)誤類(lèi)型(其中error為Error類(lèi)型) print(error) }
-
上面案例中,HTPerosn內(nèi)的
name
和age
的類(lèi)型都未遵循``CustomJSONMap
協(xié)議,所以會(huì)拋出error
image.png 當(dāng)前使用
throw
,成功將錯(cuò)誤
和正常結(jié)果
進(jìn)行分離,并了解
如何通過(guò)try
和do-catch
對(duì)結(jié)果進(jìn)行處理
。
3.2 LocalizedError
在
正常業(yè)務(wù)
開(kāi)發(fā)中,我們有時(shí)候并不滿(mǎn)足
于知道錯(cuò)誤類(lèi)型
,我還希望得到
關(guān)于這個(gè)錯(cuò)誤的描述
,以及其他信息
。
(比如網(wǎng)絡(luò)錯(cuò)誤
,我們希望獲取error能記錄errCode
、errMsg
等信息)系統(tǒng)基于
Error
協(xié)議,再公開(kāi)了一個(gè)LocalizedError
協(xié)議。
public protocol LocalizedError : Error {
/// 錯(cuò)誤的描述
var errorDescription: String? { get }
/// 錯(cuò)誤的原因
var failureReason: String? { get }
/// 恢復(fù)的建議
var recoverySuggestion: String? { get }
/// 可提供的幫助
var helpAnchor: String? { get }
}
-
所以我們可使用
LocalizedError
,把錯(cuò)誤
的信息表達(dá)
得更詳細(xì)
一些。
(枚舉
類(lèi)型的錯(cuò)誤,可以使用switch
每個(gè)屬性都獨(dú)立
給error錯(cuò)誤描述
)
image.png 我們知道
LocalizedError
就是遵守Error
的一個(gè)協(xié)議。在業(yè)務(wù)開(kāi)發(fā)中,我們完全可仿照LocalizedError
的思維
,直接自定義
一個(gè)協(xié)議
遵守Error
協(xié)議。然后自己寫(xiě)一些屬性
、函數(shù)
等。
3.3 CustomError (繼承自Error)
- OC中,系統(tǒng)提供了
NSError
錯(cuò)誤類(lèi),NSError
遵守Error協(xié)議
,可以攜帶
更多錯(cuò)誤信息
。
open class NSError : NSObject, NSCopying, NSSecureCoding {
public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil)
open var domain: String { get }
open var code: Int { get }
open var userInfo: [String : Any] { get }
open var localizedDescription: String { get }
open var localizedFailureReason: String? { get }
open var localizedRecoverySuggestion: String? { get }
open var recoveryAttempter: Any? { get }
open var helpAnchor: String? { get }
}
// NSError遵守Error協(xié)議
extension NSError : Error { }
- 在我們
swift
中,對(duì)接OCNSError
的是CustomNSError
。結(jié)構(gòu)都一樣。
public protocol CustomNSError : Error {
static var errorDomain: String { get }
var errorCode: Int { get }
var errorUserInfo: [String : Any] { get }
}
- 其實(shí)
了解到這
,我相信你,只要基于Error協(xié)議
,你想要的,都可自定義協(xié)議
來(lái)實(shí)現(xiàn)
。
3.4 rethorws
當(dāng)我們把函數(shù)
作為另一個(gè)函數(shù)入?yún)?/code>時(shí),如果
入?yún)⒑瘮?shù)
包含throws
聲明,不處理
會(huì)報(bào)錯(cuò)
:
-
【處理方式一】在
函數(shù)內(nèi)處理
錯(cuò)誤情況,外部可直接調(diào)用函數(shù)
image.png -
【處理方式二】如果
不想
在函數(shù)內(nèi)處理
錯(cuò)誤情況,可以加上rethorws
聲明,由外部處理
。
image.png
rethrows
適用于鏈?zhǔn)秸{(diào)用
時(shí),函數(shù)內(nèi)部
不需要關(guān)心異常情況
,最終
再統(tǒng)一處理
異常情況。
4. defer(延后處理)
-
defer
:用來(lái)定義以任何方式
(throw、return)離開(kāi)
代碼塊前
必須執(zhí)行
的代碼
:
image.png -
多個(gè)defer
時(shí),執(zhí)行順序
是反序執(zhí)行
(與創(chuàng)建順序相反)。
image.png
5. assert(斷言)
- 很多
編程語(yǔ)言
都有斷言機(jī)制
,不符合
指定條件就拋出運(yùn)行時(shí)錯(cuò)誤
,常用于調(diào)試(Debug)
階段的條件判斷
- 默認(rèn)情況下,
Swift斷言
只會(huì)在debug
模式下生效
,release
模式下忽略
:
image.png
本節(jié)內(nèi)容較多,涉及到Mirror
的深層探索,放到下一節(jié):Mirror源碼探索