iOS的Mantle實戰


公司項目之前的model層代碼是我使用JSON工具直接生成Objective-C代碼的,當時還是覺得相當省事的,畢竟我經歷過無model層的NSDictionary“黑暗”時期。但是隨着項目的推進,問題開始慢慢顯現出來。

於是,在一個多月前,我在Objc.io上看到提及了Mantle,花了一點時間看了一下,決定先在分支上全套改用Mantle。使用了一段時間,性能沒造成什么瓶頸,穩定性還是可以接受的。后來也基於Mantle、MK和RAC,把網絡的請求整合在一起,在新項目上全面使用。一開始時也是沒什么問題的,但后來我也逐漸發現了Mantle雖好,但不至於能解決一切問題。最近看到了《為什么唱吧iOS 6.0選擇了Mantle》文章,我決定寫博客記下一些model層的坑。

 

簡要分析

先來簡要分析一下各種構建model層方法的優點和缺點:

一、工具生成model

優點:

1、簡單易用,新手也可以10秒上手

2、有一定的容錯代碼

3、代碼生成相對工整和規范,部分工具還可以選擇是否使用ARC

4、生成簡單model耗時少

缺點:

1、工具生成的類名或者屬性名不太符合要求,往往需要自行修改,但是修改起來相當麻煩,卻需要相當專注以防有什么地方忘記修改。

2、生成的代碼相當冗長。

3、對適應字段變化比較麻煩,一旦屬性需要修改字段時,要么人工修改,要么重新生成,但是極有可能需要重復缺點1的步驟。

4、model之間一些繼承關系還是需要自行修改繼承來實現。

 

二、基於運行時生成的model(Mantle這類)

優點:

1、減少大量模版代碼

2、修改字段映射時相當簡便

3、擴展時相對方便

4、可以實現更多復雜的映射關系和數值轉換

5、調試時的異常可以較好地發現問題

6、實現了NSCopying和NSCoding協議,可以輕松序列化

缺點:

1、基於運行時屬性映射,對性能有一定影響

2、有部分容錯處理需要自行解決,否則很可能崩潰(下文詳解)

3、框架代碼不少

 

三、NSDictionary型model

優點:

1、無需任何基礎,直接可用

2、容錯性相對較高

3、無視任何數據結構,均能適應

缺點:

1、維護成本昂貴

2、編譯器無法檢查拼寫,需要定義大量key的常量,否則極其容易寫錯

3、調試相對麻煩

 

其實,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的數值類型和文檔不符乃家常便飯。作為和用戶最近的前線,我只能想盡辦法去收尾,不能完全放任不管吧。常規的方法不外乎以下幾種:

1、轉換model前先進行預處理JSON數據,把類型不符的值轉換或者刪除掉

2、為這些容易崩潰的值,都寫上NSValueTransformer的轉換

 

這些的確都能解決問題,但是效率就下降了很多,你得關注各種各樣的可能出現的情況。相信我,要是你這么做,你連睡覺都睡不好。

我列舉一下幾種類型不符會導致的異常:

1、屬性是BOOL類型,返回值是string類型。

2、屬性是NSString類型,返回值是number類型,Mantle只會轉換出NSNumber類型。你調用length等NSString的專用方法時,你懂的。

3、使用了類似上面"result.access_token"的映射,但返回值不是object類型(例如array類型)。

4、屬性是NSArray,使用了轉換,返回值是object。

 

放心,實際情況中,絕對不會只有上述4種可能的。不過幸運的是,Mantle幫大家處理了1、3、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的NSObject+BKAssociatedObjects”來動態添加屬性。這樣子,Mantle就不會把這些屬性進行序列化了。

 

小小的總結

model的框架對項目影響深遠,如果為了不在以后的項目留下坑,那model這一層還是要多加思考,免得造成了維護不能的境地。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM