(IOS) 別只將Codable用來(lái)解Json, 玩轉(zhuǎn)你的模型吧 - 從0到Double系列

logo.png

本文代碼使用Swift 4

代碼:https://github.com/jamesdouble/RandMyMod


前言

自從 Swift 4 出來(lái)之后(現(xiàn)已4.1)凭疮,相信不少讀者已經(jīng)看過無(wú)數(shù)國(guó)內(nèi)外篇的文章在介紹 Swift 4 當(dāng)中的一個(gè)新功能 Codable要拂,之所以會(huì)火扁誓,不外乎就是為一個(gè)目前普遍業(yè)務(wù)上對(duì)伺服器回調(diào)Json -> 自定義模型這個(gè)流程開了一條捷徑缕粹,也附加了不少?gòu)椥缘撼1酒恼虏皇侵赜贑odable的協(xié)議或是轉(zhuǎn)換璃诀,而是Codable能幫助我解決以下的問題,所以其他就不贅述了谷暮。

Custom Encoding and Decoding

只關(guān)心Codable可跳到用Codable隨機(jī)自定義模型

Problem

最近在工作上做了幾個(gè)類似單元開發(fā)的測(cè)試蒿往,需要大量的測(cè)試面對(duì)各型各樣的數(shù)據(jù)UI能否正常展示,需要一個(gè)能將自己模型數(shù)據(jù)隨機(jī)化的一個(gè)框架湿弦。

第一時(shí)間當(dāng)然是找找有沒有符合的框架能用????瓤漏,各種關(guān)鍵字組合搜索后,還是沒看到能達(dá)成這塊需求的(可能是我沒搜索到颊埃,在這也求介紹)蔬充,

最后還是決定自己來(lái)寫一個(gè)陽(yáng)春版的模型隨機(jī)框架。

目的

struct MyStruct {
    var opt: Int
    var opt2: Int
}

foo = 某function(MyStruct)

foo.opt  // 4242
foo.opt2 // 1234

初步Approach

要能做到把自己自定義的模型交給程序去隨機(jī)班利,就一定得從動(dòng)態(tài)調(diào)用起手饥漫,否則框架就無(wú)法得知你自定義的模型 變量名,變量的型別...等罗标,進(jìn)而無(wú)法針對(duì)你的變量打亂庸队。

  • 以下我就嘗試了幾個(gè)常用的動(dòng)態(tài)調(diào)用,比較他們各自的優(yōu)缺:

    單純使用 Mirror + Protocol 缺少諸多已知要素

    在靜態(tài)調(diào)用環(huán)境下的Swift, Mirror算是比較特殊了馒稍,雖然沒有Runtime牛(Apple管它叫做反射)皿哨,但也是個(gè)很好用的框架。

    • 優(yōu)點(diǎn):

      可兼容任何自定義的Struct, Class纽谒,且是純Swift。

    • 缺點(diǎn):

      因?yàn)椴蝗ハ拗铺囟ǖ囊阎愋腿缡洌手荒茏R(shí)別它的變量類型跟讀取變量數(shù)值鼓黔,并沒有附帶反過來(lái)賦值的通道央勒。

      性能較差。

      沒有辦法做樹狀處理澳化。

    使用Protocol手動(dòng)賦值

    需要手動(dòng)去判斷每個(gè)回傳回來(lái)的key再自行去賦值崔步,完全是不可行的方法。

    同上缺點(diǎn)缎谷,使用Mirror一定得放盡一個(gè)實(shí)例井濒,不能只傳Type,全寫出來(lái)其實(shí)也不單純了列林。

    protocol RandProtocol {
        mutating func randResult(key: String, value: Any)
        static func initRand() -> RandProtocol
    }
    
    struct JamesStruct: RandProtocol {
        var opt: Int = 0
        mutating func randResult(key: String, value: Any) {
            if key == "opt" {
                if let intvalue = value as? Int {
                    self.opt = intvalue
                }
            }
        }
        static func initRand() -> RandProtocol {
                return JamesStruct()
        }
    }
    
    class RandMyMod<T: RandProtocol> {
    
        func randByMirror() -> T {
            guard var newObject: T = T.initRand() as? T else { fatalError() }
            let mirror = Mirror(reflecting: newObject)
            for child in mirror.children {
                if child.value is Int {
                    newObject.randResult(key: child.label!, value: (Int(arc4random_uniform(100) + 1)))
                } else if child.value is Float {
                    ///
                }
            }
            return newObject
        }
    }
    
    let james = RandMyMod<JamesStruct>().randByMirror()
    james.opt
    
    

    最方便但限制多 NSObject

    • 優(yōu)點(diǎn):

      1. 繼承的Class可以共用NSObject的init()方法瑞你,這樣就不需要在使用時(shí)要傳進(jìn)一個(gè)實(shí)例,能直接從Type T 初始化一個(gè)實(shí)例 T()希痴,即可對(duì)他進(jìn)行賦值者甲。

      2. 擁有方法.value(forKey:),只要能取得變量名稱就能取得變量數(shù)值砌创,進(jìn)而用此數(shù)值判斷之后要set的Value是什么樣的類型虏缸,當(dāng)然在NSObject里要取得變量名稱也不難。

    • 缺點(diǎn):

      1. 必須要繼承 NSObject嫩实,但個(gè)人偏好使用 struct 來(lái)實(shí)作大部分的Data模型刽辙,也代表沒辦法使用任何繼承。

      2. 在此Class下的任何自定義模型的變量也必須要是NSObject甲献,才能做樹狀的隨機(jī)宰缤,否則將停止。

      class A: NSObject { 
          var foo: B  //此變量無(wú)法被識(shí)別, 也無(wú)法被更改
      }
      
      class B {
          var num: Int
      }
      

    搭配Runtime

    class James: NSObject {
        @objc var opt: Int = 0
        @objc var opt2: Int = 0
    }
    
    class RandMyMod<T: NSObject> {
        func rand() -> T {
        var count: UInt32 = 0
            var newObject: T = T()
            guard let properties = class_copyPropertyList(T.self, &count) else { fatalError() }
            for i in 0..<count {
                let pro = properties[Int(i)]
                let name = property_getName(pro)
                let str = String(cString: name)
                newObject.setValue(Int(arc4random_uniform(100) + 1), forKey: str)
            }
        return newObject
        }
    }
    let james = RandMyMod<James>().rand()
    james.opt //  == 20
    james.opt2 // == 45
    

    說(shuō)到最完善最op的動(dòng)態(tài)調(diào)用竟纳,肯定是Objective-C向的Runtime了撵溃,設(shè)置最少也最直觀,我之所以最后不采用的原因:

    1. 目前框架整體方向還是希望能以純Swift為主锥累,不想使用類OC方法缘挑。
    2. 有使用RunTime的框架對(duì)于主程序的侵入性還是較大的,希望此框架是以輔助性的工具類為主桶略。

    搭配Mirror

    class RandMyMod<T: NSObject> {
    
        func randByMirror() -> T {
            var newObject: T = T()
            let mirror = Mirror(reflecting: T())
            for child in mirror.children {
                if child.value is Int {
                    newObject.setValue(Int(arc4random_uniform(100) + 1), forKey: child.label!)
                } else if child.value is Float {
                    ///
                }
            }
            return newObject
        }
    }
    
    let james2 = RandMyMod<James>().randByMirror()
    james2.opt  // == 55
    james2.opt2  // == 74
    

    Mirror 在取得變量名稱與變量類型的判斷上明顯比看起來(lái)簡(jiǎn)易許多语淘,但他的局限性其實(shí)跟Runtime差不多,效能甚至比Runtime跟差际歼,若是變量數(shù)量較多會(huì)導(dǎo)致影響到線程的運(yùn)行惶翻。

平均需求 Codable

先說(shuō)Codable跟動(dòng)態(tài)調(diào)用其實(shí)一點(diǎn)毛關(guān)系也沒有,第一時(shí)間也是沒想到的鹅心,但平均了以上兩個(gè)的優(yōu)缺吕粗,我卻發(fā)現(xiàn)Codable能涵蓋幾個(gè)問題的優(yōu)化:

  1. Struct, Class 都能使用,因?yàn)镃odable是協(xié)議
  2. 賦值問題旭愧,說(shuō)是json自動(dòng)生成模型實(shí)例颅筋,那理解成用json自動(dòng)賦值給一個(gè)模型宙暇,沒啥毛病
  3. 沒有使用Mirror 或是 Runtime 效能消耗很低,幾乎是單純的改值而已

唯一比較沒法在更優(yōu)化(或是我沒想到)的兩點(diǎn):

  1. 無(wú)法單純使用 Codable type 去初始化一個(gè)實(shí)例
  2. 若要做樹狀的隨機(jī)议泵,變量也得是Codable占贫。
class Man: Codable {
    var name: String = ""
    var address: String = ""
    var website: [String] = []
}
    
let man = Man()
RandMyMod<Man>().randMe(baseOn: man) { (newMan) in
    guard let new = newMan else { return }
    print(new.address)  //mnxvpkalug
    print(new.name)     //iivjohpggb
    print(new.website)  //["pbmsualvei", "vlqhlwpajf", "npgtxdmfyt"]
}

Implement

整個(gè)流程很單純就是:

  1. 實(shí)例 encode 成 Data

    func randMe(baseOn instance: T, completion: (T?)-> ()) {
      let jsonData = try JSONEncoder().encode(instance)
    
  2. Data 轉(zhuǎn)成 Dictionary

    let jsonDic = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers)
    
  3. 隨機(jī)Dictionary的元素

    基本上原本最復(fù)雜的取值賦值問題,Codable都已經(jīng)大家常用的標(biāo)準(zhǔn)方法了先口,所以本框架就只需要在這一步型奥,處理樹狀的遞回隨機(jī),并著墨一些附加功能碉京。

    • 樹狀的遞回隨機(jī)

      主要處理只在這個(gè)類RandFactory

      它的init(注入一個(gè) Value: Any, key變量名: String)厢汹,外部調(diào)他的function - randData() 即可獲得跟注入同類型的,且已隨機(jī)的值收夸。

      可注入的類型包括String, Int, Float….等可隨機(jī)的類型坑匠,還包括最重要的Dictionary

      若類型是 Dictionary(類型是字典卧惜,代表他也是一個(gè)自定義的Codable模型)厘灼,遍歷里面的元素,將元素在做一次RandFactory咽瓷,并用新值更新Dictionary设凹,達(dá)到遞回。

      for (_, variable) in dictionary.enumerated() {
          let factory = RandFactory(variable.value, variable.key, specificBlock: specificBlock, delegate: delegate).randData()
          dictionary.updateValue(factory, forKey: variable.key)
      }
      
    • 附加功能

      可以做到忽略特定變量茅姜,指定類型隨機(jī)種子....等闪朱,這里不贅述

  4. Dictionary 傳成 Data

    let jsonData = try JSONSerialization.data(withJSONObject: newDicionary, options: .prettyPrinted)
    
  5. Data Decode 成 實(shí)例

    let decoder = JSONDecoder()
    let newinstance = try decoder.decode(T.self, from: jsonData)
    

Final Example

struct Man: Codable {
    var name: String = ""
    var age: Int = 40
    var website: [String] = []
    var child: Child = Child()
}

struct Child: Codable {
    var name: String = "Baby" //Baby has no name yet.
    var age: Int = 2
    var toy: Toys = Toys()
}

class Toys: Codable {
    var weight: Double = 0.0
}

extension Man: RandMyModDelegate {
    
    func shouldIgnore(for key: String, in Container: String) -> Bool {
        switch (key, Container) {
        case ("name","child"):
            return true
        default:
            return false
        }
    }
  
    func specificRandType(for key: String, in Container: String, with seed: RandType) -> (() -> Any)? {
        switch (key, Container) {
        case ("age","child"):
            return { return seed.number.randomInt(min: 1, max: 6)}
        case ("weight",_):
            return { return seed.number.randomFloat() }
        default:
            return nil
        }
    }
}

let man = Man()
RandMyMod<Man>().randMe(baseOn: man) { (newMan) in
    guard let child = newMan?.child else { print("no"); return }
    print(child.name)   //Baby
    print(child.age)    //3
    print(child.toy.weight) //392.807067871094
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钻洒,隨后出現(xiàn)的幾起案子奋姿,更是在濱河造成了極大的恐慌,老刑警劉巖素标,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件称诗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡头遭,警方通過查閱死者的電腦和手機(jī)寓免,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)计维,“玉大人袜香,你說(shuō)我怎么就攤上這事■昊蹋” “怎么了蜈首?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我疾就,道長(zhǎng)澜术,這世上最難降的妖魔是什么艺蝴? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任猬腰,我火速辦了婚禮,結(jié)果婚禮上猜敢,老公的妹妹穿的比我還像新娘姑荷。我一直安慰自己,他們只是感情好缩擂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布鼠冕。 她就那樣靜靜地躺著,像睡著了一般胯盯。 火紅的嫁衣襯著肌膚如雪懈费。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天博脑,我揣著相機(jī)與錄音憎乙,去河邊找鬼。 笑死叉趣,一個(gè)胖子當(dāng)著我的面吹牛泞边,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疗杉,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阵谚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了烟具?” 一聲冷哼從身側(cè)響起梢什,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朝聋,沒想到半個(gè)月后嗡午,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玖翅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年翼馆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片金度。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡应媚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猜极,到底是詐尸還是另有隱情中姜,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站丢胚,受9級(jí)特大地震影響翩瓜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜携龟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一兔跌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峡蟋,春花似錦坟桅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蓬戚,卻和暖如春夸楣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背子漩。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工豫喧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痛单。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓嘿棘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親旭绒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸟妙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1、隨機(jī)數(shù) 不需要隨機(jī)數(shù)種子 arc4random()%N + begin:產(chǎn)生begin~begin+N的隨機(jī)數(shù)...
    我是小胡胡123閱讀 4,161評(píng)論 0 2
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語(yǔ)閱讀 3,834評(píng)論 0 6
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,805評(píng)論 1 10
  • 132.轉(zhuǎn)換錯(cuò)誤成可選值 通過轉(zhuǎn)換錯(cuò)誤成一個(gè)可選值,你可以使用 try? 來(lái)處理錯(cuò)誤挥吵。當(dāng)執(zhí)行try?表達(dá)式時(shí),如果...
    無(wú)灃閱讀 1,251評(píng)論 0 3
  • 今天上午公司組織了一場(chǎng)國(guó)學(xué)文化分享活動(dòng)重父,請(qǐng)來(lái)了臺(tái)灣的國(guó)學(xué)老師,跟大家講講《論語(yǔ)》忽匈,談?wù)勼w會(huì)房午。 聽完還是挺有感觸的,...
    語(yǔ)竹123閱讀 338評(píng)論 1 1