本文主要介紹Mirror
的使用以及使用Mirror進(jìn)行JSON解析的錯(cuò)誤處理
反射Mirror
反射:是指可以動(dòng)態(tài)獲取類型荒适、成員信息
绸硕,在運(yùn)行時(shí)
可以調(diào)用方法琼讽、屬性等行為的特性徘熔,
在上面的分析中,我們已經(jīng)知道霉猛,對(duì)于一個(gè)純swift類來(lái)說(shuō),并不支持直接像OC runtime那樣的操作
但是swift標(biāo)準(zhǔn)庫(kù)依舊提供了反射機(jī)制珠闰,用來(lái)訪問(wèn)成員信息惜浅,即
Mirror
一般使用
class CJLTeacher: NSObject {
var age: Int = 18
}
let mirror = Mirror(reflecting: CJLTeacher.self)
for pro in mirror.children{
print("\(pro.label): \(pro.value)")
}
- 運(yùn)行上面代碼,發(fā)現(xiàn)沒(méi)有任何打印伏嗜,為什么坛悉?是因?yàn)?code>Mirror中傳入的參數(shù)不對(duì),應(yīng)該是
傳入實(shí)例對(duì)象
承绸,修改如下
class CJLTeacher: NSObject {
var age: Int = 18
}
var t = CJLTeacher()
//傳入t也可以
let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{
print("\(pro.label): \(pro.value)")
}
查看Mirror定義
- 進(jìn)入
Mirror
初始化方法裸影,發(fā)現(xiàn)傳入的類型是Any
,則可以直接傳t
public init(reflecting subject: Any)
- 進(jìn)入
children
public let children: Mirror.Children
??
//進(jìn)入Children军熏,發(fā)現(xiàn)是一個(gè)AnyCollection轩猩,接收一個(gè)泛型
public typealias Children = AnyCollection<Mirror.Child>
??
//進(jìn)入Child,發(fā)現(xiàn)是一個(gè)元組類型,由可選的標(biāo)簽和值構(gòu)成均践,
public typealias Child = (label: String?, value: Any)
這也是為什么能夠通過(guò)label
晤锹、value
打印的原因。即可以在編譯時(shí)期且不用知道任何類型信息情況下彤委,在Child
的值上用Mirror
去遍歷
整個(gè)對(duì)象的層級(jí)視圖
JSON解析
根據(jù)Mirror的這個(gè)特性鞭铆,我們思考下,可以通過(guò)Mirror
做什么焦影?首先想到的是JSON解析
车遂,如下所示,我們定義了一個(gè)CJLTeacher類斯辰,然后通過(guò)一個(gè)test方法來(lái)解析t
class CJLTeacher {
var age = 18
var name = "CJL"
}
<!--JSON解析-->
func test(_ obj: Any) -> Any{
let mirror = Mirror(reflecting: obj)
//判斷條件 - 遞歸終止條件
guard !mirror.children.isEmpty else {
return obj
}
//字典
var keyValue: [String: Any] = [:]
//遍歷
for children in mirror.children {
if let keyName = children.label {
//遞歸調(diào)用
keyValue[keyName] = test(children.value)
}else{
print("children.label 為空")
}
}
return keyValue
}
<!--使用-->
var t = CJLTeacher()
print(test(t))
代碼的打印結(jié)果如下舶担,打印出了key-value
JSON解析封裝
如果我們想大規(guī)模的使用上述的JSON解析,上面只是針對(duì)CJLTeacher的JSON解析椒涯,所以柄沮,為了方便其他類使用,可以將JSON解析抽取成一個(gè)協(xié)議废岂,然后提供一個(gè)默認(rèn)實(shí)現(xiàn)祖搓,讓類遵守協(xié)議
//1、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2湖苞、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典拯欧,用于存儲(chǔ)json數(shù)據(jù)
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{
print("key是nil")
}
}else{
print("當(dāng)前-\(children.value)-沒(méi)有遵守協(xié)議")
}
}
return keyValue
}
}
//3、讓類遵守協(xié)議(注意:類中屬性的類型也需要遵守協(xié)議财骨,否則無(wú)法解析)
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
}
//使用
var t = CJLTeacher()
print(t.jsonMap())
【問(wèn)題】:運(yùn)行代碼發(fā)現(xiàn)镐作,并不是我們想要的結(jié)果,原因是因?yàn)镃JLTeacher中屬性的類型也需要遵守CustomJSONMap
協(xié)議
【修改】:所以在原有基礎(chǔ)上增加以下代碼
//Int、String遵守協(xié)議
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
修改后的運(yùn)行結(jié)果如下
錯(cuò)誤處理
為了讓我們自己封裝的JSON解析更好用隆箩,除了對(duì)正常返回的處理该贾,還需要對(duì)其中的錯(cuò)誤進(jìn)行處理,在上面的封裝中捌臊,我們目前采用的是print打印的杨蛋,這樣并不規(guī)范,也不好維護(hù)及管理理澎。那么如何在swift中正確的表達(dá)錯(cuò)誤呢逞力?
首先,Swift中糠爬,提供了Error協(xié)議
來(lái)標(biāo)識(shí)當(dāng)前應(yīng)用程序發(fā)生錯(cuò)誤
的情況寇荧,其中Error
的定義如下
public protocol Error {
}
Error
是一個(gè)空協(xié)議,其中沒(méi)有任何實(shí)現(xiàn)执隧,這也就意味著你可以遵守該協(xié)議揩抡,然后自定義錯(cuò)誤類型户侥。所以不管是我們的struct、Class捅膘、enum
添祸,我們都可以遵循
這個(gè)Error
來(lái)表示一個(gè)錯(cuò)誤
所以接下來(lái),對(duì)我們上面封裝的JSON解析修改其中的錯(cuò)誤處理
- 定義一個(gè)
JSONMapError
錯(cuò)誤枚舉寻仗,將默認(rèn)實(shí)現(xiàn)的print替換成枚舉類型
//定義錯(cuò)誤類型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
//1刃泌、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典署尤,用于存儲(chǔ)json數(shù)據(jù)
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.emptyKey
}
}else{
return JSONMapError.notConformProtocol
}
}
return keyValue
}
}
- 但是這里有一個(gè)問(wèn)題耙替,jsonMap方法的返回值是
Any
,我們無(wú)法區(qū)分返回的是錯(cuò)誤還是json數(shù)據(jù)
曹体,那么該如何區(qū)分呢俗扇?即如何拋出錯(cuò)誤
呢?在這里可以通過(guò)throw關(guān)鍵字
(即將Demo中原本return
的錯(cuò)誤箕别,改成throw
拋出)
//2铜幽、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲(chǔ)json數(shù)據(jù)
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{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
-
發(fā)現(xiàn)改成throw拋出錯(cuò)誤后串稀,編譯器提示有錯(cuò)除抛,其原因是因?yàn)榉椒ú](méi)有聲明成
throws
所以還需要在方法的返回值箭頭前增加
throws
(表示將方法中錯(cuò)誤拋出),修改后的默認(rèn)實(shí)現(xiàn)代碼如下所示
//1母截、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
//2到忽、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲(chǔ)json數(shù)據(jù)
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{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
- 由于我們?cè)趈sonMap方法中遞歸調(diào)用了自己清寇,所以還需要在遞歸調(diào)用前增加
try
關(guān)鍵字
//2喘漏、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲(chǔ)json數(shù)據(jù)
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()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
<!--使用時(shí)需要加上try-->
var t = CJLTeacher()
print(try t.jsonMap())
到此华烟,一個(gè)完整的錯(cuò)誤表達(dá)方式就完成了
swift中錯(cuò)誤處理的方式
swift中錯(cuò)誤處理的方式主要有以下兩種:
-
【方式一】:使用
try關(guān)鍵字
翩迈,是最簡(jiǎn)便的,即甩鍋盔夜,將這個(gè)拋出給別人(向上拋出帽馋,拋給上層函數(shù))。但是在使用時(shí)比吭,需要注意以下兩點(diǎn):-
try?
返回一個(gè)可選類型
,只有兩種結(jié)果:要么
成功
姨涡,返回具體的字典值
要么
錯(cuò)誤
衩藤,但并不關(guān)心是哪種錯(cuò)誤,統(tǒng)一返回nil
try!
表示你對(duì)這段代碼有絕對(duì)的自信涛漂,這行代碼絕對(duì)不會(huì)發(fā)生錯(cuò)誤
-
-
【方式二】:使用
do...catch
【方式一】try
通過(guò)try
來(lái)處理JSON解析的錯(cuò)誤
//CJLTeacher中定義一個(gè)height屬性赏表,并未遵守協(xié)議
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
var height = 1.85
}
/*****1检诗、try? 示例*****/
var t = CJLTeacher()
print(try? t.jsonMap())
/*****打印結(jié)果*****/
nil
/*****2、try! 示例*****/
var t = CJLTeacher()
print(try! t.jsonMap())
/*****打印結(jié)果*****/
Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
2020-12-20 18:27:28.305112+0800 05-MirrorAndError[18642:1408258] Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
<!--3瓢剿、如果直接使用try逢慌,是向上拋出-->
var t = CJLTeacher()
try t.jsonMap()
/*****打印結(jié)果*****/
Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
2020-12-20 18:31:24.837476+0800 05-MirrorAndError[18662:1410970] Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
從上面可以知道,錯(cuò)誤是向上拋出的间狂,即拋給了上層函數(shù)攻泼,如果上層函數(shù)也不處理,則直接拋給main鉴象,main沒(méi)有辦法處理忙菠,則直接報(bào)錯(cuò),例如下面的例子
//使用
var t = CJLTeacher()
func test() throws -> Any{
try t.jsonMap()
}
try test()
其運(yùn)行結(jié)果如下
方式二:do-catch
通過(guò)do-catch
來(lái)處理JSON解析的錯(cuò)誤
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print(error)
}
運(yùn)行結(jié)果如下
LocalError協(xié)議
如果只是用Error還不足以表達(dá)更詳盡的錯(cuò)誤信息纺弊,可以使用LocalizedError
協(xié)議牛欢,其定義如下
public protocol LocalizedError : Error {
/// A localized message describing what error occurred.錯(cuò)誤描述
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 }
}
- 繼續(xù)修改上面的JSON解析,對(duì)
JSONMapError
枚舉增加一個(gè)擴(kuò)展淆游,遵守LocalizedError
協(xié)議傍睹,可以打印更詳細(xì)的錯(cuò)誤信息
//定義更詳細(xì)的錯(cuò)誤信息
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key為空"
case .notConformProtocol:
return "沒(méi)有遵守協(xié)議"
}
}
}
<!--使用-->
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print(error.localizedDescription)
}
運(yùn)行結(jié)果如下
CustomNSError協(xié)議
CustomNSError
相當(dāng)于OC中的NSError
,其定義如下犹菱,有三個(gè)默認(rèn)屬性
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解析中的
JSONMapError
拾稳,讓其遵守CustomNSError
協(xié)議,如下所示
//CustomNSError相當(dāng)于NSError
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
<!--使用-->
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print((error as? JSONMapError)?.errorCode)
}
運(yùn)行結(jié)果如下
總結(jié)
Error
是swift中錯(cuò)誤類型的基本協(xié)議已亥,其中LocalizedError
熊赖、CustomNSError
都遵守Error
-
如果在方法中,想要同時(shí)
返回正常值
和錯(cuò)誤
虑椎,需要通過(guò)throw
返回錯(cuò)誤震鹉,并且在方法返回值的箭頭前面加throws
關(guān)鍵字,再調(diào)用方法時(shí)捆姜,還需要加上try關(guān)鍵字
传趾,或者使用do-catch
,使用try
時(shí)泥技,有以下兩點(diǎn)需要注意:try?
返回的是一個(gè)可選類型浆兰,要么成功,返回正常值珊豹,要么失敗簸呈,返回nil
try! 表示你對(duì)自己的代碼非常自信,絕對(duì)不會(huì)發(fā)生錯(cuò)誤店茶,一旦發(fā)生錯(cuò)誤蜕便,就會(huì)崩潰
使用建議:建議使用
try?
,而不是try!
最后附上完整的自定義JSON解析代碼
//定義錯(cuò)誤類型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
//定義更詳細(xì)的錯(cuò)誤信息
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key為空"
case .notConformProtocol:
return "沒(méi)有遵守協(xié)議"
}
}
}
//CustomNSError相當(dāng)于NSError
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
//1贩幻、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
//2轿腺、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典两嘴,用于存儲(chǔ)json數(shù)據(jù)
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()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
//3、讓類遵守協(xié)議(注意:類中屬性的類型也需要遵守協(xié)議族壳,否則無(wú)法解析)
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
var height = 1.85
}
//使用
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print((error as? JSONMapError)?.errorCode)
}