前言
上篇文章Swift 內(nèi)存管理 & Runtime講解了Runtime的一個應(yīng)用場景:Swift類可繼承NSObject
,配合使用@objc
修飾符击困,讓OC端的Runtime API
可調(diào)用Swift類的方法與屬性涎劈,同時使用Dynamic
修飾符广凸,也可實現(xiàn)方法交換
,只不過這個交換是在編譯期
就確定的脸哀。
那么問題來了撞蜂,既然Swift是一門靜態(tài)語言
蝌诡,那到底它有沒有類似OC這樣的Runtime運行時機制
呢浦旱?當然有,就是本篇文章即將介紹的Mirror
反射甥捺,雖然沒有OC的Runtime
強大镰禾,但是也能在運行時
獲取對象的類型
和成員變量
羡微,其中第三方庫HandyJSON
就是基于Mirror
的機制來實現(xiàn)的。
一盯蝴、Mirror反射
什么是反射
捧挺??? 可以動態(tài)
獲取類型
翅睛、成員
信息捕发,在運行時
可以調(diào)用方法扎酷、屬性
等行為的特性。
老規(guī)矩凡纳,還是先上示例代碼??
class LGTeacher {
var age: Int = 18
var name: String = "Luoji"
}
let mirror = Mirror(reflecting: LGTeacher().self)
for pro in mirror.children{
print("\(pro.label ?? ""): \(pro.value)")
}
上述代碼是
Mirror
的一個簡單的應(yīng)用場景 ?? 通過reflecting
初始化,接著通過.children
讀取屬性名與值狞尔。
1.1 Mirror定義
接下來我們看看Mirror相關(guān)文檔的API的定義??
init(reflecting: Any)
傳入的類型是Any
偏序。
-
.children
接著查看代碼??
/// A collection of `Child` elements describing the structure of the
/// reflected subject.
public let children: Mirror.Children
繼續(xù)查看Children
??
/// The type used to represent substructure.
///
/// When working with a mirror that reflects a bidirectional or random access
/// collection, you may find it useful to "upgrade" instances of this type
/// to `AnyBidirectionalCollection` or `AnyRandomAccessCollection`. For
/// example, to display the last twenty children of a mirror if they can be
/// accessed efficiently, you write the following code:
///
/// if let b = AnyBidirectionalCollection(someMirror.children) {
/// for element in b.suffix(20) {
/// print(element)
/// }
/// }
public typealias Children = AnyCollection<Mirror.Child>
最后看Child
??
/// An element of the reflected instance's structure.
///
/// When the `label` component in not `nil`, it may represent the name of a
/// stored property or an active `enum` case. If you pass strings to the
/// `descendant(_:_:)` method, labels are used for lookup.
public typealias Child = (label: String?, value: Any)
所以端朵,回到示例代碼冲呢,通過print("\(pro.label ?? ""): \(pro.value)")
打印的就是key
與value
敬拓。
1.2 JSON解析
既然Mirror
可以解析出類的屬性key
與value
厕诡,那它能做什么灵嫌? ?? 第一時間聯(lián)想到的就是JSON解析
寿羞。還是先看以下解析的實例代碼??
class LGPerson {
var name = "Luoji"
var age = 18
var student = LGStudent() // 持有對象
}
class LGStudent {
var score = 100.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) // 遞歸(model嵌套的場景)
}else{
print("children.label ")
}
}
return keyValue
}
let t = LGPerson()
print(test(t))
代碼不難形病,也是通過reflecting
初始化mirror對象量瓜,然后讀取其children
屬性值绍傲,存儲到keyValue字典并返回烫饼。其中做了判空
,以及遞歸
處理(處理屬性也是model的場景)比藻。
優(yōu)化一
上述的代碼银亲,是在一個swift命名空間中
聲明的解析方法
务蝠,我們想讓其通用化
赠尾,基于封裝
的思想气嫁,自然的想到寸宵,將其抽離成一個協(xié)議
,這樣讓每個model遵循該協(xié)議甲棍,那么model就具備了JSON解析的功能感猛。廢話不多說陪白,直接上代碼??
//1、定義一個JSON解析協(xié)議
protocol LGJSONMap {
func jsonMap() -> Any
}
//2序厉、在extension中實現(xiàn)jsonMap()解析方法
extension LGJSONMap {
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典弛房,用于存儲json數(shù)據(jù)
var keyValue: [String: Any] = [:]
//遍歷
for children in mirror.children {
if let value = children.value as? LGJSONMap {
if let keyName = children.label {
//遞歸
keyValue[keyName] = value.jsonMap()
}else{
print("key是nil")
}
}else{
print("當前-\(children.value)-沒有遵守協(xié)議")
}
}
return keyValue
}
}
//3、讓類遵守協(xié)議(注意:類中屬性的類型也需要遵守協(xié)議拄轻,否則無法解析)
class LGPerson : LGJSONMap {
var name = "Luoji"
var age = 18
var student = LGStudent() // 持有對象
}
class LGStudent : LGJSONMap {
var score = 100.0
}
//調(diào)用
var t = LGPerson()
print(t.jsonMap())
運行??
上圖可知恨搓,沒有成功常拓,提示屬性并沒有遵循協(xié)議,那么根據(jù)錯誤提示宪郊,我們修改代碼??
extension Int: LGJSONMap{}
extension String: LGJSONMap{}
extension Double: LGJSONMap{}
完美解決懊亡!這里將解析方法抽離成協(xié)議的方法
店枣,是一種封裝
的思想鸯两。既然提到協(xié)議
甩卓,那么我們再順便講解下Swift中的Protocol協(xié)議
的用法吧。
Protocol
Swift的協(xié)議
比OC的協(xié)議
功能多多了宅此,非常強大父腕,不僅可以聲明屬性璧亮、函數(shù)
枝嘶,支持可選實現(xiàn)
群扶,還可以在extension中
完成屬性
和函數(shù)
的默認實現(xiàn)
(上述JSON解析就是這樣的場景)缴饭。
所以颗搂,以后大家在開發(fā)過程中峭火,如果碰到了類似的這樣的場景,也可以封裝成協(xié)議的方法稍浆,很好的隔離代碼
衅枫,而且讓代碼看起來非常簡潔
弦撩。??
遇到一些
通用功能
的函數(shù),我們可以使用協(xié)議封裝
起來点晴,只要遵循這個協(xié)議陪竿,就可以直接調(diào)用相應(yīng)的屬性和函數(shù)
族跛。
優(yōu)化二
上述解析方法中礁哄,對于錯誤
的處理方式姐仅,只是簡單的print
一下掏膏,那有沒有一種更好的方式馒疹,將這些錯誤封裝
成一個對象生均,并拋出來
給調(diào)用方马胧,讓調(diào)用方自行處理呢?當然有??
可以通過
系統(tǒng)Error
協(xié)議搭配throw
關(guān)鍵字威彰,優(yōu)雅的拋出錯誤或返回常規(guī)結(jié)果歇盼,讓開發(fā)者自己選擇處理方式。
二盈咳、Error處理
在Swift中,系統(tǒng)提供Error協(xié)議
來表示當前應(yīng)用程序發(fā)生錯誤的情況丈积,并支持使用throw關(guān)鍵字
,優(yōu)雅的拋出錯誤厌均。
2.1 Error協(xié)議
public protocol Error {
}
上面源碼可知 ?? Error是一個空
協(xié)議晶密,其中沒有任何實現(xiàn)懂牧,這也就意味著你可以遵守該協(xié)議尊勿,然后自定義錯誤類型
躯保。所以不管是Struct
吻氧、Class
盯孙、enum
,我們都可以遵循這個Error協(xié)議來表示一個錯誤骑晶。我們就先用枚舉enum
來標識錯誤(枚舉的功能也十分強大草慧,后續(xù)會有專門的一篇文章來講解
)桶蛔。
- 先定義錯誤類型
enum LGJSONMapError: Error{
case emptyKey
case notConformProtocol
}
- 然后調(diào)用
extension LGJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲json數(shù)據(jù)
var keyValue: [String: Any] = [:]
//遍歷
for children in mirror.children {
if let value = children.value as? LGJSONMap {
if let keyName = children.label {
//遞歸
keyValue[keyName] = value.jsonMap()
}else{
return LGJSONMapError.emptyKey
}
}else{
return LGJSONMapError.notConformProtocol
}
}
return keyValue
}
}
但是有個問題漫谷,現(xiàn)在return的數(shù)據(jù)仔雷,無法區(qū)分是正常的dic還是錯誤枚舉,如何處理呢舔示??? 可以利用throw
關(guān)鍵字拋出錯誤
??
extension LGJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典碟婆,用于存儲json數(shù)據(jù)
var keyValue: [String: Any] = [:]
//遍歷
for children in mirror.children {
if let value = children.value as? LGJSONMap {
if let keyName = children.label {
//遞歸
keyValue[keyName] = value.jsonMap()
}else{
throw LGJSONMapError.emptyKey
}
}else{
throw LGJSONMapError.notConformProtocol
}
}
return keyValue
}
}
將return
改成throw
后,編譯器會報錯??
根據(jù)錯誤的提示信息公给,是因為我們聲明的函數(shù)沒有聲明throw
匣沼,那么我們修改函數(shù)聲明??
protocol LGJSONMap {
func jsonMap() throws -> Any
}
仍舊會報錯它匕,用了throw沒有用try烧给,那么補齊??
我們把屬性遵循協(xié)議的注釋掉,運行看看是否會拋出錯誤??
果然拋出了LGJSONMapError.notConformProtocol
錯誤平项。
Error處理的方式
Swift中錯誤處理的方式主要有以下兩種:
- 使用
try - throw
最簡便的园担,即甩鍋,將這個拋出給別人(向上拋出鸽嫂,拋給上層調(diào)用方函數(shù))癣籽。
但是需要注意以下兩點:-
try?
?? 返回一個可選類型
塑顺,只有兩種結(jié)果:要么成功,返回具體的值
格二;要么錯誤滔吠,但并不關(guān)心是哪種錯誤,統(tǒng)一返回nil
-
print(try? t.jsonMap())
-
try!
?? 表示你對這段代碼有絕對的自信属愤,這行代碼絕對不會發(fā)生錯誤
print(try! t.jsonMap())
從上面可以知道驳阎,錯誤是向上拋出
的饵隙,即拋給了調(diào)用函數(shù),如果調(diào)用的上層函數(shù)也不處理购披,則直接拋給main
,main
沒有辦法處理,則直接報錯
垢揩。
- 使用
do - catch
其中do
處理正確結(jié)果
悬包,catch
處理error
订咸,catch有個隱藏參數(shù)
践宴,就是error
(Error類型)
do {
// 處理正常結(jié)果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
// 處理錯誤類型(其中error為Error類型)
print(error)
}
LocalizedError
上述的錯誤處理中,只知道了錯誤類型
敷扫,如果還想知道錯誤相關(guān)的描述
或其它信息
,則需要使用LocalizedError
膀值,定義??
/// Describes an error that provides localized messages describing why
/// an error occurred and provides more information about the error.
public protocol LocalizedError : Error {
/// A localized message describing what error occurred.
var errorDescription: String? { get }
/// A localized message describing the reason for the failure.
var failureReason: String? { get }
/// A localized message describing how one might recover from the failure.
var recoverySuggestion: String? { get }
/// A localized message providing "help" text if the user requests help.
var helpAnchor: String? { get }
}
接下來,我們來使用一下這個協(xié)議茬缩,修改上面的解析代碼??
//定義錯誤類型
enum LGJSONMapError: Error{
case emptyKey
case notConformProtocol
}
extension LGJSONMapError : LocalizedError {
var errorDescription: String?{
switch self {
case .emptyKey:
return "key為空"
case .notConformProtocol:
return "沒有遵守協(xié)議"
}
}
}
//調(diào)用
var t = LGPerson()
do {
// 處理正常結(jié)果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
// 處理錯誤類型(其中error為Error類型)
print(error.localizedDescription)
}
完美,錯誤描述被打印了出來殴边!
CustomNSError協(xié)議
還有一個CustomNSError協(xié)議
栗恩,相當于OC中的NSError
,其定義如下洪燥,有三個默認屬性??
/// Describes an error type that specifically provides a domain, code,
/// and user-info dictionary.
public protocol CustomNSError : Error {
/// The domain of the error.
static var errorDomain: String { get }
/// The error code within the given domain.
var errorCode: Int { get }
/// The user-info dictionary.
var errorUserInfo: [String : Any] { get }
}
接著我們繼續(xù)修改JSON解析中的LGJSONMapError磕秤,讓其遵守CustomNSError協(xié)議??
extension LGJSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
//調(diào)用
var t = LGPerson()
do {
// 處理正常結(jié)果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
// 處理錯誤類型(其中error為Error類型)
// print(error.localizedDescription)
print((error as? LGJSONMapError)?.errorCode)
}
rethorws
rethrows是處理這種場景的錯誤 ?? 函數(shù)1
是函數(shù)2
的入?yún)?/code>捧韵,其中
函數(shù)1
中有throws錯誤
市咆。
上代碼
func add(_ a: Int, _ b: Int) throws -> Int {
return a + b
}
func exec(_ f:(Int, Int) throws -> Int, _ a: Int, _ b: Int) rethrows {
print(try f(a, b) )
}
do {
try exec(add, 1, 2)
}catch {
print(error.localizedDescription)
}
rethrows將函數(shù)參數(shù)add的錯誤再次拋出,統(tǒng)一交由外部do - catch處理再来。
defer(延后處理)
func functionDefer() {
print("begin")
defer {
print("defer")
}
print("end")
}
函數(shù)執(zhí)行完成之前
才會執(zhí)行defer代碼塊內(nèi)部的邏輯蒙兰。如果有多個defer代碼塊磷瘤,執(zhí)行順序怎么樣?
func functionDefer() {
print("begin")
defer {
print("defer1")
}
defer {
print("defer2")
}
print("end")
}
defer代碼塊的執(zhí)行順序是逆序
的搜变。
assert(斷言)
很多編程語言都有斷言機制
采缚,不符合指定條件
就拋出運行時錯誤
,常用于調(diào)試(Debug)階段
的條件判斷
挠他。例如??
func devide(_ a: Int, _ b: Int) -> Int {
assert(b != 0, "除數(shù)不能為0")
return a / b
}
默認情況下扳抽,Swift斷言只會在debug模式下生效,release模式下忽略??
總結(jié)
本篇文章主要利用Swift類中的Mirror
反射機制殖侵,封裝了一套JSON解析協(xié)議
贸呢,同時將解析時錯誤也進行了封裝處理,順便講解了Error
的幾個常用的協(xié)議拢军。