本文代碼使用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能幫助我解決以下的問題
,所以其他就不贅述了谷暮。
只關(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):
繼承的Class可以共用NSObject的init()方法瑞你,這樣就
不需要在使用時(shí)要傳進(jìn)一個(gè)實(shí)例
,能直接從Type T 初始化一個(gè)實(shí)例 T()希痴,即可對(duì)他進(jìn)行賦值者甲。擁有方法
.value(forKey:)
,只要能取得變量名稱就能取得變量數(shù)值砌创,進(jìn)而用此數(shù)值判斷之后要set的Value是什么樣的類型虏缸,當(dāng)然在NSObject里要取得變量名稱也不難。
-
缺點(diǎn):
必須要繼承 NSObject嫩实,但個(gè)人偏好使用 struct 來(lái)實(shí)作大部分的Data模型刽辙,也代表沒辦法使用任何繼承。
在此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è)置最少也最直觀,我之所以最后不采用的原因:- 目前框架整體方向還是希望能以純Swift為主锥累,不想使用類OC方法缘挑。
- 有使用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)化:
- Struct, Class 都能使用,因?yàn)镃odable是協(xié)議
- 賦值問題旭愧,說(shuō)是json自動(dòng)生成模型實(shí)例颅筋,那理解成用json自動(dòng)賦值給一個(gè)模型宙暇,沒啥毛病
- 沒有使用Mirror 或是 Runtime 效能消耗很低,幾乎是單純的改值而已
唯一比較沒法在更優(yōu)化(或是我沒想到)的兩點(diǎn):
- 無(wú)法單純使用 Codable type 去初始化一個(gè)實(shí)例
- 若要做樹狀的隨機(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è)流程很單純就是:
-
實(shí)例 encode 成 Data
func randMe(baseOn instance: T, completion: (T?)-> ()) { let jsonData = try JSONEncoder().encode(instance)
-
Data 轉(zhuǎn)成 Dictionary
let jsonDic = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers)
-
隨機(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ī)種子....等闪朱,這里不贅述
-
-
Dictionary 傳成 Data
let jsonData = try JSONSerialization.data(withJSONObject: newDicionary, options: .prettyPrinted)
-
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
}