-
寫在前面
我并沒(méi)有專門寫一個(gè)swift下KVC的開源庫(kù)脸甘,伸手黨們可以command+W了
我做的優(yōu)化是針對(duì)自己寫的一個(gè)相對(duì)比較重的MVC庫(kù)里的KVC優(yōu)化析苫,之前寫的太簡(jiǎn)單了黔帕。
-
進(jìn)入正題
優(yōu)化之前的KVC代碼:
func autoKVCBinding(_ dictionary:Dictionary<String, Any>?) {
if dictionary != nil {
let mirror:Mirror = Mirror(reflecting: self);
for (label,value) in mirror.children {
if label != nil && dictionary![label!] != nil {
if value is LBItem && dictionary![label!] is Dictionary<String, Any> {
let subItem = value as! LBItem;
subItem.autoKVCBinding(dictionary![label!] as? Dictionary<String, Any>);
} else {
self.setValue(dictionary![label!], forKey: label!);
}
}
}
}
}
功能很簡(jiǎn)單冲泥,單純只做了空值校驗(yàn)氧猬,property如果還是同名model的話可以做遞歸的KVC校驗(yàn)妈嘹。
-
那么柳琢,第一個(gè)問(wèn)題,我們要做的是什么润脸?
通俗點(diǎn)說(shuō)就是我們需要做上哪些新功能柬脸,達(dá)到哪些目標(biāo)。
這是我在動(dòng)手之前思考的一個(gè)問(wèn)題毙驯,先明確需求倒堕,要做哪些新功能,有目標(biāo)的去找解決方案尔苦。
這方面我列了幾條
- 解決類型不同的轉(zhuǎn)換問(wèn)題
- 解決泛型Array的賦值問(wèn)題(Array<XXModel>)
- 父類和父類的父類賦值問(wèn)題涩馆,往前遞歸幾層行施?是否做限制?
- 過(guò)濾Foundation框架里的屬性
明確了問(wèn)題之后就可以找解決方案了魂那,首先在OC下由于有強(qiáng)大牛逼的runtime和完善的Foundation的API蛾号,這幾個(gè)問(wèn)題都不是問(wèn)題,不過(guò)還是記錄一下吧:
- 類型不同的轉(zhuǎn)換問(wèn)題:解決這個(gè)問(wèn)題要單獨(dú)把不同類型拉出來(lái)涯雅,如果是數(shù)字類型轉(zhuǎn)string鲜结,可以直接取stringValue,也可以直接stringWithFormat活逆,如果是string轉(zhuǎn)數(shù)字類型精刷,則可以統(tǒng)一用NSNumberFormatter處理,轉(zhuǎn)成NSNumber即可
- 泛型Array的賦值問(wèn)題蔗候,runtime可以上場(chǎng)了怒允,先獲取當(dāng)前class的所有屬性,拿到objc_property_t類型锈遥,property_getAttributes這個(gè)API拿到的字符串中是包含NSArray<XXXModel>這種泛型字段的纫事,直接把NSArray的元素類型截出來(lái),再轉(zhuǎn)換成Class對(duì)象就可以了所灸。
- 父類的問(wèn)題丽惶,這里我思考了很久,網(wǎng)上幾個(gè)庫(kù)的解決方案不一樣爬立,有的是直接遞歸到NSObject層钾唬,再在屬性篩選的時(shí)候把系統(tǒng)API給去掉,有的是在遞歸的時(shí)候加個(gè)判斷侠驯,遞歸到Foundation的時(shí)候就break抡秆,我比較了一下覺(jué)得后者性能更好,減少了遞歸次數(shù)陵霉,但是沒(méi)有實(shí)驗(yàn)的地方是如果子類override了Foundation框架中的屬性會(huì)怎么樣琅轧,不過(guò)這么做的情況也很少,我也就暫時(shí)忽略掉了踊挠。
- Foundation屬性的過(guò)濾乍桂,這個(gè)在上一層父類遞歸的時(shí)候就已經(jīng)篩選掉了,因?yàn)椴粫?huì)遞歸到Foundation的類上面去效床,自然也不會(huì)出現(xiàn)Foundation的屬性睹酌。
剛才說(shuō)的是OC的方案,也就是使用runtime的方案剩檀,那么在swift下這個(gè)方案可不可行呢憋沿?
我實(shí)驗(yàn)了一下,Swift中的原生Array在property_getAttributes這個(gè)可以獲取泛型的API拿到的類型是NSArray沪猴,同時(shí)NSArray在這個(gè)API下拿到的也是NSArray辐啄,區(qū)分不開采章,好了,那我就拋棄runtime找找其他的解決方案吧壶辜。
首先找到的庫(kù)是Reflection悯舟,這個(gè)庫(kù)很吊很牛逼,牛逼到我現(xiàn)在還沒(méi)完全看懂它的代碼(當(dāng)然是我太菜了)砸民,里面大概的思路是拿到Swift下面的類的C指針抵怎,然后對(duì)指針去做偏移,從而動(dòng)態(tài)拿到這個(gè)類的屬性的一系列信息岭参,這個(gè)庫(kù)我看了下確實(shí)滿足了我所有需求反惕,但是秉持著作死的精神,我還是想自己找到一個(gè)看得懂的解決方案出來(lái)演侯。
第二個(gè)庫(kù)是GrandModel姿染,這個(gè)整體的方案和我之前的方案大致相似:通過(guò)Mirror動(dòng)態(tài)拿屬性列表,拿完之后用NSObject的setValue(_:forKey:)去做賦值秒际,那看來(lái)我的方案是可以實(shí)現(xiàn)我的需求的盔粹!那就這么做了!
由于加入了Swift的原生類型程癌,所以不能用Foundation框架里的API去做了,我想到的方案是Mirror的subjectType可以拿到當(dāng)前類的類型轴猎,那么我就需要遍歷當(dāng)前類的所有property嵌莉,對(duì)每個(gè)value取mirror,去拿類型再存起來(lái)捻脖。
- 類型轉(zhuǎn)換問(wèn)題锐峭,數(shù)字類型轉(zhuǎn)String,直接走"\(value)"可婶,這個(gè)簡(jiǎn)單沿癞,string轉(zhuǎn)數(shù)字這個(gè)需要對(duì)每個(gè)數(shù)字類型進(jìn)行判斷,如果是Int要調(diào)Int的init矛渴,如果是Double要調(diào)double的init椎扬,這個(gè)還有優(yōu)化空間。
- 泛型Array的處理具温,通過(guò)Mirror拿到的subjectType也是包含元素類型的信息的蚕涤,由于swift的所有Array和Dictionary必須要注明元素類型,所以我這里要做一個(gè)分支處理:如果Array的元素類型是Model铣猩,而且實(shí)際從服務(wù)端傳入的value也是[String:Any]的情況下才進(jìn)行Array的遍歷KVC揖铜,如果上面的條件不滿足,那么就要判斷類Array的元素類型和服務(wù)端value的元素類型是否一致达皿,如果不一致直接賦值會(huì)有crash風(fēng)險(xiǎn)天吓,核心代碼如下:
if valueType.hasPrefix("Array") {
let arrayClassName = valueType.substring(with: valueType.index(valueType.startIndex, offsetBy: 6)..<valueType.index(valueType.endIndex, offsetBy: -1));
if arrayClassName.hasPrefix("Dictionary<String") {
if property.arrayClass != nil && property.arrayClass == .item {
ret = type(of: self).itemsWithArray(value as! Array<Dictionary<String, Any>>);
} else {
//arrayClass不是item贿肩,無(wú)需作數(shù)組泛型KVC,直接賦值龄寞,此處有因?yàn)镈ictionary的泛型不一致導(dǎo)致的crash風(fēng)險(xiǎn)汰规,待處理
ret = value;
}
} else {
ret = value;
}
}
- 父類的處理,我這里直接拋棄了Swift原生類型的繼承的處理萄焦,我覺(jué)得自己繼承Array去寫一個(gè)類的情況也比較少控轿,就簡(jiǎn)單處理了,沿用上面OC的處理就行了拂封,只是OC取父類是用runtime的API茬射,Swift里我用的是superMirror。
- Foundation屬性的過(guò)濾冒签,這一條和OC處理方法一致在抛,在父類遞歸取屬性的時(shí)候,如果父類的類型以NS開頭萧恕,就直接break就行了刚梭。
-
后續(xù)的工作
這次正好碰上離職,還有很多點(diǎn)沒(méi)做完票唆,比如Dictionary類型的強(qiáng)校驗(yàn)朴读,如果服務(wù)端給過(guò)來(lái)一個(gè)[String: String],本地是[String: Int]走趋,這種情況是有crash風(fēng)險(xiǎn)的衅金,本地要么做篩查,要么做轉(zhuǎn)換簿煌。
還有服務(wù)端字段名和本地屬性名不一致的話需要做映射氮唯。
還有取properties的時(shí)候的緩存。
還有是否需要throw出error姨伟,這個(gè)swift的新特性我還沒(méi)怎么用過(guò)呢惩琉。