Swift
是靜態語言
,他不能像OC一樣,直接獲取對象的屬性和方法,但是Swift
標準庫依舊提供了反射機制
,用來訪問成員信息,即Mirror
。
一、Mirror反射
反射:是指可以動態獲取類型、成員信息,在運行時可以調用方法、屬性等行為的特性。
使用Mirror
的初始化方法reflecting
,接著通過.children
讀取屬性名與值
。代碼:
class Animal {
var age: Int = 18
var name: String = "Cat"
}
let mirror = Mirror(reflecting: Animal().self)
for pro in mirror.children{
print("\(pro.label ?? ""): \(pro.value)")
}
//打印結果:
//age: 18
//name: Cat
1.1 Mirror源碼
進入Mirror
初始化方法,發現傳入的類型是Any
,則可以直接傳t
:
// Creates a mirror that reflects on the given instance.
// - Parameter subject: The instance for which to create a mirror.
public init(reflecting subject: Any)
查看children
:
/// A collection of `Child` elements describing the structure of the reflected subject.
public let children: Mirror.Children
??
//進入Children,發現是一個AnyCollection,接收一個泛型
public typealias Children = AnyCollection<Mirror.Child>
??
//進入Child,發現是一個元組類型,由可選的標簽和值構成,
public typealias Child = (label: String?, value: Any)
聯系示例代碼,通過print("\(pro.label ?? ""): \(pro.value)")
打印的就是key
與value
。
1.2 JSON解析
我們定義了一個Animal
類,然后通過一個test
方法來解析:
class Animal {
var age = 18
var name = "Cat"
}
//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 {
//遞歸調用
keyValue[keyName] = test(children.value)
}else{
print("children.label 為空")
}
}
return keyValue
}
var kv: [String: Any] = test(Animal()) as! [String : Any]
print(kv)
//打印結果:
//["name": "Cat", "age": 18]
為了方便擴展、通用
,我們可以利用封裝
的思想,將其抽象為一個協議
,然后提供一個默認實現,讓類遵守協議,進而,model就具備了JSON解析
的功能。
//1、定義一個JSON解析協議
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2、提供一個默認實現
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲json數據
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("當前-\(children.value)-沒有遵守協議")
}
}
return keyValue
}
}
//3、讓類遵守協議(注意:類中屬性的類型也需要遵守協議,否則無法解析)
class Animal: CustomJSONMap {
var age = 18
var name = "Cat"
}
//使用
var t = Animal()
print(t.jsonMap())
//打印結果:
//當前-18-沒有遵守協議
//當前-Cat-沒有遵守協議
//[:]
沒有成功,提示,屬性沒有遵守協議。添加代碼:
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
extension Double: CustomJSONMap{}
//打印結果:
//["age": 18, "name": "Cat"]
HandyJson
二、Error
為了JSON
處理更規范,更好的維護和管理,我們需要對錯誤更規范化的處理。Swift
,提供了Error協議
來標識當前應用程序發生錯誤的情況。其中Error
的定義如下:
//A type representing an error value that can be thrown.
public protocol Error { }
如上,Error
是一個空協議
,其中沒有任何實現,這也就意味著你可以遵守該協議,然后自定義錯誤類型
。所以,struct
、Class
、enum
等,都可以遵循這個Error
來表示一個錯誤。
下面,我們結合JSON解析
,做一下錯誤處理:
//定義錯誤類型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
//1、定義一個JSON解析協議
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2、提供一個默認實現
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲json數據
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
}
}
這里有一個問題,jsonMap
方法的返回值是Any
,我們無法區分返回的是錯誤還是json
數據,那么該如何區分,即如何拋出錯誤呢?在這里可以通過throw
關鍵字,將Demo中原本return的錯誤,改成throw拋出
。
//1、定義一個JSON解析協議
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
//2、提供一個默認實現
//2、提供一個默認實現
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存儲json數據
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
}
}
因為我們使用了throw
,所以還需要在遞歸調用前增加 try
關鍵字。
//使用
var t = Animal()
print(try t.jsonMap())
三、錯誤處理方式
Swift
處理錯誤的方式,主要有兩種,try - throw
,do - catch
。
3.1 try - throw
使用try關鍵字
,是最簡便的,將Error
向上拋出,拋給上層函數。但是在使用時,需要注意以下兩點:
-
try?
:返回一個可選類型
,成功,返回具體的字典值;錯誤,但并不關心是哪種錯誤,統一返回nil
。 -
try!
:表示你對這段代碼有絕對的自信,這行代碼絕對不會發生錯誤。
//CJLTeacher中定義一個height屬性,并未遵守協議
class Animal: CustomJSONMap {
var age = 18
var name = "CJL"
var height = 1.85
}
let t = Animal()
//1、try?
print(try? t.jsonMap())
//打印結果:nil
//2、try!
print(try! t.jsonMap())
//打印結果:
//SwiftTest/main.swift:256: Fatal error: 'try!' expression unexpectedly raised an error: SwiftTest.JSONMapError.notConformProtocol
//2022-08-29 23:14:16.107880+0800 SwiftTest[10620:17556994] SwiftTest/main.swift:256: Fatal error: 'try!' expression unexpectedly raised an error: SwiftTest.JSONMapError.notConformProtocol
//3、直接使用try,是向上拋出-
try t.jsonMap()
//打印結果:
//Swift/ErrorType.swift:200: Fatal error: Error raised at top level: SwiftTest.JSONMapError.notConformProtocol
//2022-08-29 23:14:47.827688+0800 SwiftTest[10648:17557924] Swift/ErrorType.swift:200: Fatal error: Error raised at top level: SwiftTest.JSONMapError.notConformProtocol
從上面可以知道,try
錯誤是向上拋出的,即拋給了上層函數,如果上層函數也不處理,則直接拋給main,main沒有辦法處理,則直接報錯
。
3.2 do - catch
其中do
處理正確結果
,catch
處理error
,catch
有個隱藏參數,就是error(Error類型)
do {
// 處理正常結果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
// 處理錯誤類型(其中error為Error類型)
print(error)
}
//打印結果:notConformProtocol
3.3 LocalizedError
以上代碼,只打印了錯誤類型
,如果還想知道錯誤相關的描述或其它信息
,則需要使用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 }
}
接下來,我們來使用這個協議
,繼續修改上面JSON的解析代碼,新增代碼如下:
//定義更詳細的錯誤信息
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key為空"
case .notConformProtocol:
return "沒有遵守協議"
}
}
}
do {
// 處理正常結果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
// 處理錯誤類型(其中error為Error類型)
print(error.localizedDescription)
}
//打印結果:沒有遵守協議
3.3 CustomNSError
CustomNSError
協議,相當于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 }
}
繼續修改JSON解析中的JSONMapError
,讓其遵守CustomNSError協議
,如下:
//CustomNSError相當于NSError
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
do {
// 處理正常結果
try t.jsonMap() // 這里try不會報錯了,因為錯誤都分給了catch
} catch {
//1、 處理錯誤類型(其中error為Error類型)
print(error.localizedDescription)
//2、處理錯誤類型(其中error為Error類型
print((error as? CustomNSError)?.errorCode as Any)
}
//catch1 打印結果:沒有遵守協議
//catch2 打印結果:Optional(504)
rethorws
rethrows
是處理這種場景的錯誤:函數1是函數2的入參,其中函數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)
}
defer(延后處理)
func functionDefer() {
print("begin")
defer {
print("defer1")
}
defer {
print("defer2")
}
print("end")
}
functionDefer()
//打印結果: begin end defer2 defer1
函數執行完成之前
,才會執行defer
代碼塊內部的邏輯。如果有多個defer
代碼塊,defer代碼塊的執行順序是逆序
的。