Swift Mirror 類型實踐 —— 快速將類轉換為 Json

前一段時間看 Chris Eidhof 的個人網站的時候,注意到一篇博客。
使用 Swift Mirror 快速將類轉換為 Json 的方法。
Swift Mirrors and JSON
但是原文已經不適應 Swift 2.2 的版本了,我將修改過的代碼貼在這里(還是不少要改的),并附上一些可能需要解釋的部分的注釋:

//定義的示范用的結構體
struct Person {
    var name: String = "John"
    var age: Int = 50
    var dutch: Bool = false
    var address: Address? = Address(street: "Market St.")
    var Some: Int = 60
}

struct Address {
    var street: String
}

let john = Person()

//可以轉換為 Json 的協議
protocol JSON {
    func toJSON() throws -> AnyObject?
}

//沒有實現 JSON 協議的錯誤類型
enum CouldNotSerializeError {
    case NoImplementation(source: Any, type: Mirror)
}

extension CouldNotSerializeError: ErrorType { }

//使用 extension 對 JSON 添加方法的默認實現
extension JSON {
    func toJSON() throws -> AnyObject? {
        //創建 Mirror 類型
        let mirror = Mirror(reflecting: self)
        //Mirror 的 children 是一個 (label: String?, value: Any) 元組類型,表示該類的所有屬性的名字和類型
        if mirror.children.count > 0  {
            var result: [String:AnyObject] = [:]
            for case let (label?, value) in mirror.children {
                //如果這個類型沒有實現 JSON 協議,則拋出錯誤
                if let value = value as? JSON {
                    //遞歸調用
                    result[label] = try value.toJSON()
                } else {
                    throw CouldNotSerializeError.NoImplementation(source: self, type: Mirror(reflecting: value))
                }
            }
            return result
        }
         //如果沒有屬性(一般類,String,Int 等),則直接返回
        return self as? AnyObject
    }
}

//將一般類型都遵從 JSON 協議
extension Person: JSON { }
extension String: JSON { }
extension Int: JSON { }
extension Bool: JSON { }
extension Address: JSON { }

//Optionai 需要特別對待,原因是如果直接返回,則會是 .Some: [...] 
extension Optional: JSON {
    func toJSON() throws -> AnyObject? {
        if let x = self {
            if let value = x as? JSON {
                return try value.toJSON()
            }
            throw CouldNotSerializeError.NoImplementation(source: x, type: Mirror(reflecting: x))
        }
        return nil
    }
}

do {
    print(try john.toJSON())
} catch {
    print(error)
}
//輸出:Optional({ Some = 60;  address =  { street = "Market St." ; }; age = 50; dutch = 0; name = John; })

是不是非常方便?我自己想了一個更加方便的,只要一個方法:

//泛型
func mkJson<T>(a: T?) -> AnyObject? {
    //如果為空,則不將該屬性加入 Json
    guard let a = a else { return nil }
    let mirror = Mirror(reflecting: a)
    let num = mirror.children.count
    if num > 0  {
        var result: [String:AnyObject] = [:]
        for case let (label?, value) in mirror.children {
            //這里有點 Trick,如果是一個有值的 Optional(Some),則直接載入warp下的值。
            //后一個條件是防止如果有類型是叫做 Some(一般不會有吧)的屬性的情況,見注1
            if label == "Some" && mirror.children.count == 1 {
                return toJson(value)
            }
            result[label] = toJson(value)
        }
        return result
    }
    
    return a as? AnyObject
}

let json = mkJson(john)
print(json)
//得到的結果完全一樣
//注1:也就是說,如果你有一個類或者結構體,里面只有一個屬性叫做 Some ,那么這個 Some 會被跳過直接讀取里面的類型。當然,這種情況微乎其微。

唯一不太好的就是,使用 Mirror 有性能上的損失,我在Xcode上跑了一下測試,用的我自己的一個成員類
用時如下:
類創建:

func testInit() {
    self.measureBlock{
        for _ in 1...2000 {
            let _ = User()
        }
    }
}
//0.400 (0.401) seconds

Mirror:

func testMirror() {
    self.measureBlock {
        let user = User()
        for _ in 1...2000 {
            mkJson(user)
        }
    }
}
//4.147 (4.147) seconds
//我的天吶

if let 方式加入 dictionary:

func testIfLet() {
    self.measureBlock {
        let user = User()
        for _ in 1...2000 {
            let _ = user.parameters
        }
    }
}

extension User {

public var parameters: [String: AnyObject]  {
    var parms:[String : AnyObject] = [ : ]
    if let email = email {
        parms["email"] = email
    }
    if let nickName = nickName {
        parms["nickName"] = nickName
    }
    if let phoneNumber = phoneNumber {
        parms["phoneNumber"] = phoneNumber
    }
    return parms
}

//in 0.531 (0.532) seconds
//嗯……

所以說,雖然方便,還是慎用吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • 嗯哼嗯哼蹦擦擦~~~ 轉載自:https://github.com/Tim9Liu9/TimLiu-iOS 目錄 ...
    philiha閱讀 4,992評論 0 6
  • 2017年12月10日 晴 星期日 俗話說,鐵打的營盤流水的兵。對于醫院來說,就是鐵打的病房,流水的病人。不出家門...
    泥鰍的戀愛閱讀 266評論 2 10
  • 今天真不錯。好高興,好快樂。陪老哥一起去辦事,總是聊起小時候的事情。好懷念…… 小的時候沒有勇氣出來看看故...
    當感覺變舊閱讀 558評論 0 0
  • 在那遙遠的蘑菇林里,有個叫李嘉慧的神仙,她是夢神。當我正昏昏欲睡的時候,她仙女般出現在我面前。她非常美麗,...
    明大作文葉老師閱讀 443評論 0 0