上一節(jié)步氏,我們分析了閉包 & Runtime & Any等類型响禽。
介紹Runtime運(yùn)行時
時,我們知道swift
是靜態(tài)語言
荚醒,但可兼容OC類
實(shí)現(xiàn)objc_msgSend
消息發(fā)送機(jī)制芋类。
-
swift
中dynamic
聲明的函數(shù)可在extension
中進(jìn)行函數(shù)替換
。不過這個替換
在編譯期
就完成
了腌且。
那梗肝,swift
有沒有自己的運(yùn)行時機(jī)制
呢?
- 有铺董,
Mirror
反射機(jī)制巫击。雖然沒
有OC運(yùn)行時
那么強(qiáng)大
。但支持運(yùn)行時
獲取對象
的類型
和成員變量
(HandyJSON
就是基于Mirror思想
精续,直接從內(nèi)存讀取
來實(shí)現(xiàn)的??)
本節(jié)坝锰,我們先體驗(yàn)一下Mirror
,下一節(jié)重付,我們深入研究Mirror底層原理
- Mirror體驗(yàn)
- protocol協(xié)議
- Error錯誤機(jī)制(try顷级、throws、rethrows)
- defer
- assert
1. Mirror體驗(yàn)
-
Mirror
(反射)确垫,可以動態(tài)
獲取類型
弓颈、成員變量
,在運(yùn)行時
可以調(diào)用方法
删掀、屬性
等行為的特性
翔冀。
對于一個純swift類
來說,并不支持
我們直接像OCRuntime
那樣操作披泪。但Swift標(biāo)準(zhǔn)庫
給我們提供
了簡單實(shí)用
的反射機(jī)制
纤子。
- 測試案例:
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是一個
struct
結(jié)構(gòu)體
image.pngMirror 初始化時,入?yún)?code>Any類型
image.png
children
屬性為(label: String?, value: Any)
元組類型
image.png
-
Mirror
的用途很多
,最常用的是用于JSON解析
:
class HTPerson {
var name = "ht"
var age = 18
var student = HTStudent() // 持有對象
}
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] = [:] // 記錄屬性名和屬性內(nèi)容
for children in mirror.children{
if let keyName = children.label {
keyValue[keyName] = test(children.value) // 遞歸(繼續(xù)遍歷person中的對象屬性)
}else{
print("children.label ")
}
}
return keyValue
}
let t = HTPerson()
print(test(t))
-
打印結(jié)果:
image.png
分析:
mirror
成功讀取對象
的屬性名
和屬性值
控硼,可使用字典
存儲和輸出泽论;每個
屬性
都會默認(rèn)
當(dāng)作對象
來處理,通過mirror.children
可判斷當(dāng)前對象
是否擁有屬性
卡乾,沒有就跳出當(dāng)前屬性
的遞歸
流程翼悴。 這樣可保證
所有讀取children
的完整層級信息
。(ps:
遞歸
實(shí)際是利用棧
特性说订,直接
或間接
調(diào)用自身
實(shí)現(xiàn)層級讀取
的需求
抄瓦,遞歸需要
有明確的終止條件
)
- 上面代碼有個
不好
的點(diǎn):錯誤結(jié)果
是print
打印潮瓶,外界
并不知道
陶冷。
在swift
中,錯誤信息
一般使用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ù)
描焰,會顯得非常麻煩
媳否。
協(xié)議
可以幫我們默認(rèn)實(shí)現(xiàn)
這個方法
,只要對象遵守
這個協(xié)議
荆秦,都可以直接使用
篱竭。
【思路】
- 創(chuàng)建一個協(xié)議(
CustomJSONMap
),聲明jsonMap
函數(shù)步绸,并在extension
中默認(rèn)實(shí)現(xiàn)jsonMap
掺逼。- 創(chuàng)建一個枚舉
JSONMapError
,對所有錯誤類型
進(jìn)行列舉
瓤介。jsonMap
內(nèi)部支持Mirror
所有遵循CustomJSONMap
協(xié)議的屬性吕喘。對未遵循協(xié)議
的屬性和屬性名為空
的屬性返回
相應(yīng)的錯誤類型
。
// 錯誤枚舉
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
類型氯质,他們沒有遵守
CustomJSONMap協(xié)議。
image.png 既然如此漾月,我們讓
String
和Int
都遵守CustomJSONMap
協(xié)議病梢,再運(yùn)行。
extension String: CustomJSONMap {}
extension Int: CustomJSONMap {}
-
完美打印
所有內(nèi)容
在我們的開發(fā)中,
protocol協(xié)議
是一大利器
蜓陌,使用非常方便觅彰。
- 遇到一些
通用
的函數(shù)
,我們可以使用協(xié)議包裝
起來钮热,只要遵循
這個協(xié)議
填抬,就可以直接調(diào)用
相應(yīng)的屬性
和函數(shù)
。很好的隔離代碼
隧期,而且讓代碼
看起來非常簡潔
飒责。
- 上面的
錯誤信息
,我們只是使用枚舉
統(tǒng)一羅列
仆潮,return時
宏蛉,常規(guī)結(jié)果
和錯誤類型
混在一起返回,讓我們很不舒服
性置。
如何解決拾并?
- 可以通過系統(tǒng)
Error
協(xié)議搭配throw
關(guān)鍵字,優(yōu)雅的拋出錯誤
或返回常規(guī)結(jié)果
鹏浅,讓開發(fā)者
自己選擇處理
方式嗅义。
3. Error機(jī)制(try、throws隐砸、rethrows)
swift
中之碗,系統(tǒng)提供Error
協(xié)議來表示當(dāng)前應(yīng)用程序
發(fā)生錯誤的情況
,并支持使用throw
關(guān)鍵字季希,優(yōu)雅
的拋出錯誤
褪那。
3.1 基礎(chǔ)Error協(xié)議
public protocol Error { }
- 系統(tǒng)的
Error協(xié)議
,就是一個空的公共協(xié)議
胖眷, 不管是class
武通、struct
還是enum
,都可以通過遵守
這個協(xié)議
珊搀,來表達(dá)錯誤
冶忱。
以 enum
為例:
// 錯誤枚舉,遵循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,會error
var age = 18 // Int未繼承CustomJSONMap,會error
}
let t = HTPerson()
// do...catch 分開處理囚枪,catch中有默認(rèn)參數(shù)error。
do {
// 處理正常結(jié)果
print(try t.jsonMap())
} catch {
// 處理錯誤類型(其中error為Error類型)
print(error)
}
//print(try t.jsonMap()) // try : 向上甩鍋劳淆,將錯誤拋給上層函數(shù)處理(上層沒處理链沼,就crash)
print(try? t.jsonMap()) // try?: 只關(guān)注正常結(jié)果,忽略錯誤(如果錯誤沛鸵,就為nil, 不執(zhí)行后續(xù)流程)
//print(try! t.jsonMap()) // try!: 對代碼絕對自信括勺,一定不會發(fā)生錯誤(如果發(fā)生缆八,直接crash)
分析:
- 讓
JSONMapError
枚舉遵守Error
協(xié)議- 將
jsonMap
函數(shù)的return 錯誤
,全部改為throw
拋出錯誤疾捍,只有正常結(jié)果
才使用return
返回奈辰。- 使用
throw
拋出錯誤會發(fā)現(xiàn),函數(shù)需要使用throws
標(biāo)注乱豆,這是告訴使用者
奖恰,這個函數(shù)可能拋出error
,需要開發(fā)者
決定是否處理宛裕。- 使用
throw
后瑟啃,發(fā)現(xiàn)不能直接調(diào)用jsonMap
,需要使用try
關(guān)鍵字來嘗試調(diào)用揩尸。
- 那
try
什么意思呢蛹屿?如何使用
呢?
try有三種使用方式:
- try : 向上甩鍋疲酌,將
錯誤
拋給上層
函數(shù)處理
(最上層都沒處理
蜡峰,就crash
)- try?: 只關(guān)注
正常結(jié)果
,忽略
所有錯誤
(如果錯誤朗恳,就為nil, 不執(zhí)行后續(xù)流程)- try!: 程序員對代碼
絕對自信
,一定不會
發(fā)生錯誤
(如果發(fā)生载绿,直接crash
)
- 上面三種方式粥诫,只是說到了
拋出
和不管
,那如何正確處理
throw拋出的錯誤
呢崭庸?
do...catch:
- 我們可以通過
do...catch
來處理怀浆。其中do
處理正確結(jié)果
,catch
處理error
怕享,catch有個隱藏參數(shù)
执赡,就是error
(Error類型)do { // 處理正常結(jié)果 try t.jsonMap() // 這里try不會報錯了,因?yàn)殄e誤都分給了catch } catch { // 處理錯誤類型(其中error為Error類型) print(error) }
-
上面案例中函筋,HTPerosn內(nèi)的
name
和age
的類型都未遵循``CustomJSONMap
協(xié)議沙合,所以會拋出error
image.png 當(dāng)前使用
throw
,成功將錯誤
和正常結(jié)果
進(jìn)行分離跌帐,并了解
如何通過try
和do-catch
對結(jié)果進(jìn)行處理
首懈。
3.2 LocalizedError
在
正常業(yè)務(wù)
開發(fā)中,我們有時候并不滿足
于知道錯誤類型
谨敛,我還希望得到
關(guān)于這個錯誤的描述
究履,以及其他信息
。
(比如網(wǎng)絡(luò)錯誤
脸狸,我們希望獲取error能記錄errCode
最仑、errMsg
等信息)系統(tǒng)基于
Error
協(xié)議,再公開了一個LocalizedError
協(xié)議。
public protocol LocalizedError : Error {
/// 錯誤的描述
var errorDescription: String? { get }
/// 錯誤的原因
var failureReason: String? { get }
/// 恢復(fù)的建議
var recoverySuggestion: String? { get }
/// 可提供的幫助
var helpAnchor: String? { get }
}
-
所以我們可使用
LocalizedError
泥彤,把錯誤
的信息表達(dá)
得更詳細(xì)
一些紊搪。
(枚舉
類型的錯誤,可以使用switch
每個屬性都獨(dú)立
給error錯誤描述
)
image.png 我們知道
LocalizedError
就是遵守Error
的一個協(xié)議全景。在業(yè)務(wù)開發(fā)中耀石,我們完全可仿照LocalizedError
的思維
,直接自定義
一個協(xié)議
遵守Error
協(xié)議爸黄。然后自己寫一些屬性
滞伟、函數(shù)
等。
3.3 CustomError (繼承自Error)
- OC中,系統(tǒng)提供了
NSError
錯誤類炕贵,NSError
遵守Error協(xié)議
梆奈,可以攜帶
更多錯誤信息
。
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
中称开,對接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é)議
來實(shí)現(xiàn)
焰轻。
3.4 rethorws
當(dāng)我們把函數(shù)
作為另一個函數(shù)入?yún)?/code>時,如果
入?yún)⒑瘮?shù)
包含throws
聲明昆雀,不處理
會報錯
:
-
【處理方式一】在
函數(shù)內(nèi)處理
錯誤情況辱志,外部可直接調(diào)用函數(shù)
image.png -
【處理方式二】如果
不想
在函數(shù)內(nèi)處理
錯誤情況,可以加上rethorws
聲明狞膘,由外部處理
揩懒。
image.png
rethrows
適用于鏈?zhǔn)秸{(diào)用
時,函數(shù)內(nèi)部
不需要關(guān)心異常情況
挽封,最終
再統(tǒng)一處理
異常情況已球。
4. defer(延后處理)
-
defer
:用來定義以任何方式
(throw、return)離開
代碼塊前
必須執(zhí)行
的代碼
:
image.png -
多個defer
時场仲,執(zhí)行順序
是反序執(zhí)行
(與創(chuàng)建順序相反)和悦。
image.png
5. assert(斷言)
- 很多
編程語言
都有斷言機(jī)制
,不符合
指定條件就拋出運(yùn)行時錯誤
渠缕,常用于調(diào)試(Debug)
階段的條件判斷
- 默認(rèn)情況下鸽素,
Swift斷言
只會在debug
模式下生效
,release
模式下忽略
:
image.png
本節(jié)內(nèi)容較多亦鳞,涉及到Mirror
的深層探索馍忽,放到下一節(jié):Mirror源碼探索