公司項目以前的model層代碼是我使用JSON工具直接生成Objective-C代碼的,當時仍是以爲至關省事的,畢竟我經歷過無model層的NSDictionary「黑暗」時期。可是隨着項目的推動,問題開始慢慢顯現出來。git
因而,在一個多月前,我在Objc.io上看到說起了Mantle,花了一點時間看了一下,決定先在分支上全套改用Mantle。使用了一段時間,性能沒形成什麼瓶頸,穩定性仍是能夠接受的。後來也基於Mantle、MK和RAC,把網絡的請求整合在一塊兒,在新項目上全面使用。一開始時也是沒什麼問題的,但後來我也逐漸發現了Mantle雖好,但不至於能解決一切問題。最近看到了《爲何唱吧iOS 6.0選擇了Mantle》文章,我決定寫博客記下一些model層的坑。github
簡要分析數組
先來簡要分析一下各類構建model層方法的優勢和缺點:網絡
1、工具生成model數據結構
優勢:框架
一、簡單易用,新手也能夠10秒上手工具
二、有必定的容錯代碼性能
三、代碼生成相對工整和規範,部分工具還能夠選擇是否使用ARCui
四、生成簡單model耗時少spa
缺點:
一、工具生成的類名或者屬性名不太符合要求,每每須要自行修改,可是修改起來至關麻煩,卻須要至關專一以防有什麼地方忘記修改。
二、生成的代碼至關冗長。
三、對適應字段變化比較麻煩,一旦屬性須要修改字段時,要麼人工修改,要麼從新生成,可是極有可能須要重複缺點1的步驟。
四、model之間一些繼承關係仍是須要自行修改繼承來實現。
2、基於運行時生成的model(Mantle這類)
優勢:
一、減小大量模版代碼
二、修改字段映射時至關簡便
三、擴展時相對方便
四、能夠實現更多複雜的映射關係和數值轉換
五、調試時的異常能夠較好地發現問題
六、實現了NSCopying和NSCoding協議,能夠輕鬆序列化
缺點:
一、基於運行時屬性映射,對性能有必定影響
二、有部分容錯處理須要自行解決,不然極可能崩潰(下文詳解)
三、框架代碼很多
3、NSDictionary型model
優勢:
一、無需任何基礎,直接可用
二、容錯性相對較高
三、無視任何數據結構,均能適應
缺點:
一、維護成本昂貴
二、編譯器沒法檢查拼寫,須要定義大量key的常量,不然極其容易寫錯
三、調試相對麻煩
其實,NSDictionary型model仍是有必定用途的,畢竟有些狀況下,不須要浪費精力去構建一個很短小或者很快就會被釋放的model。但大多數狀況下,仍是須要去構建一個合理的model,來保證項目的健壯性和開發效率。之前,我老大和我說,iOS應用MVC三層,M這一層其實服務端已經幫你完成了大部分,來到客戶端再本身處理model,既消耗性能又下降了開發效率。當時,我以爲仍是比較正確的,但隨着MVC的C變得臃腫不堪,M變得愈來愈輕量的時候。不少東西都耦合在controller,model這層能作好的話,就能必定程度上減輕了controller的複雜度。加上工做了之後發現,一個只有NSDictionary,無真正model的商業應用,真的很是不利於維護。
基於上述的種種理由,我仍是決定了正式全面使用Mantle。但Mantle不是萬能的,我仍是遇到了幾個問題。
null值
若是你的屬性是基本數值類型的話,JSON返回一個null值,那麼在Mantle生成model的時候,果斷崩潰了。這個問題和解決方案跟《爲何唱吧iOS 6.0選擇了Mantle》中的同樣,model中實現一下setNilValueForKey
:方法便可。建議使用基類繼承,那麼寫一次這個方法就全部model都解決了這個問題。
鍵值的合理映射
1 { 2 "code": 1 3 "result":{ 4 "access_token":"m_xxxxxxx", 5 "user_id":1111 6 } 7 }
例如上述JSON,假設整個JSON是一個model,那麼若是直接按照JSON的格式來映射,就要新建一個「result」額外的model類。但或許不須要這麼繁複,其實能夠這麼寫
1 + (NSDictionary *)JSONKeyPathsByPropertyKey 2 { 3 return @{@"accessToken": @"result.access_token", 4 @"userId": @"result.user_id"}; 5 }
這樣作就能夠很方便地映射到對應的屬性上,同時也不須要額外新建一個model類。這裏爲何沒寫「code」的映射呢,由於若是屬性名和JSON的鍵名一致時,是能夠省略不寫映射的,具體你們能夠看看Mantle的源碼。
值的類型問題
這個問題是最棘手的,不能說後臺坑隊友,可是JSON的數值類型和文檔不符乃屢見不鮮。做爲和用戶最近的前線,我只能想盡辦法去收尾,不能徹底聽任無論吧。常規的方法不外乎如下幾種:
一、轉換model前先進行預處理JSON數據,把類型不符的值轉換或者刪除掉
二、爲這些容易崩潰的值,都寫上NSValueTransformer的轉換
這些的確都能解決問題,可是效率就降低了不少,你得關注各類各樣的可能出現的狀況。相信我,要是你這麼作,你連睡覺都睡很差。
我列舉一下幾種類型不符會致使的異常:
一、屬性是BOOL類型,返回值是string類型。
二、屬性是NSString類型,返回值是number類型,Mantle只會轉換出NSNumber類型。你調用length等NSString的專用方法時,你懂的。
三、使用了相似上面"result.access_token"的映射,但返回值不是object類型(例如array類型)。
四、屬性是NSArray,使用了轉換,返回值是object。
放心,實際狀況中,絕對不會只有上述4種可能的。不過幸運的是,Mantle幫你們處理了一、三、4這些狀況(若是object和array都是使用了轉換方法的話,在轉換的時候會處理這些異常的),只會在調試模式下拋出異常,Release的時候是不會崩潰的。若是你們不想在調試的時候被這些異常打斷的話,能夠註釋掉MTLValidateAndSetValue這個方法中的對應代碼。
接下來你們可能以爲太匪夷所思了,爲何一個小小的客戶端還得由於用個Mantle就要去規避這麼多陷阱。我就是遇到這麼多陷阱,想到了解決方法,本身也是成長了。針對JSON的類型,我有了如下的考慮:
JSON其實就是隻有4種類型,string、number(int 、bool…)、object、array。在Objective-C對應也就是,NSString、NSNumber、NSDictionary、NSArray,所以要規避類型問題也是從這幾個類着手。這裏要說一下爲何屬性有int、bool等,我只歸了一類NSNumber。由於實質上,Mantle只是轉換了NSNumber類型的對象出來,在setValue的時候,是由系統根據類型調用了對應的NSNumber方法。因爲number和string的類型錯誤是最多見,同時也是最隱蔽的(調用相似intValue的方法看不出端倪),爲此我寫了一個AvoidMTLModelCrash的category,github上的地址,使用了這個category之後,關於NSString和NSNumber類型問題的崩潰基本均可以解決。若是是NSArray或者NSDictionary設置到NSString的屬性也是沒法檢測的,可是面對如此嚴重的類型問題,我建議仍是和小夥伴一塊兒坐下來,好好談談「人生」吧。
靈活的轉換
例如某些接口返回的數據是數組,但不少時候只須要用到這個數組的第一個元素。咱們能夠直接將數組裏面的一個元素影射出來。
1 + (NSValueTransformer *)pointListJSONTransformer 2 { 3 return [MTLValueTransformer transformerWithBlock:^id(NSArray *array) { 4 return [array firstObject]; 5 }]; 6 }
除了屬性映射和每一個屬性固定的類型轉換,MTLJSONSerializing的協議還有classForParsingJSONDictionary這麼一個方法能夠改變解釋後的類。例如B、C均繼承A,用Mantle生成A類對象。A類能夠經過這個方法,選擇不一樣的子類生成對象。可是對外可見的接口也是A類的接口,這樣就相似NSArray同樣(NSArray其實也是有不少子類的)。
序列化
因爲,Mantle已經實現了序列化的協議,因此不須要額外的代碼便可直接序列化。可是,若是你的屬性中有不能直接序列化的類型或者不想使用序列化的時候,能夠聲明爲私有成員,或者使用BlockKit的
小小的總結
model的框架對項目影響深遠,若是爲了避免在之後的項目留下坑,那model這一層仍是要多加思考,省得形成了維護不能的境地。