讓Json和Model相處的融洽點
iOS裡伺服器返回來的JsonData通過內建的json解析類或者什麼JsonKit,SBJson一類的架構都可以方便的完成解析工作,如果不覺得麻煩的話從這裡開始通過KVC取值就完全可以搞定了~可對字串這種東西進行寫入程式碼...
所以轉成Model對象還是比較必要的~而json對象到model對象的映射也有很多東西可以做了~諸如JSONModel,Mantle什麼的~最值得一提的是傳智播客的當家花旦李明傑老師所寫的MJExtension~非常出色,尤其在轉化效率上~不敢說今天拿出來的這東西比MJExtension好,只能說更符合筆者自己簡單粗暴有效開發和使用習慣~
經過測試,用我的Macbook對後文給出的1000個相同的測試詞典所組成的數組進行映射操作,MJExtension耗時僅0.07秒,而我寫的這套工具則花了0.25秒左右...耗時在3~4倍的區間~由於是通過對於模型名稱和類的名稱進行判斷和字串處理整合來進行映射,更高的耗時也是自然的~通過GCD的並行作業進行處理也並沒有太明顯的效率提升~
考慮後得到的結論是對每一個模型進行轉化的時候都需要對齊內部結構進行完整掃描,對於單個模型的轉化這是自然,但是對於數組的整體映射,由於數組儲存的模型是相同的,那麼長度1000的數組有999次掃描都是沒必要的~1:1的線性耗時增加造成了整體的效率降低~作為第一版就先這樣吧,畢竟即使沒有通過緩衝類結構來提升效率,1000個模型0.25秒的映射速度目前來看也還是可以接受的~
接下來說說這究竟是個什麼東西
先看一組用來做測試的假資料
-(NSDictionary *)fakeData{ return@{@name:@jack,@id:@123,@code:@4567890-,@other:@{@attr1:@value1,@attr2:@150,@attr3:@0.8},@test:@3,@d:@4.5,@l:@3456789,@f:@123.123,@b:@1,@date1:@13567890456,@date2:@2015-4-3 11:18:24,@arr:@[ @{@attr1:@value1,@attr2:@150,@attr3:@0.8}, @{@attr1:@value1,@attr2:@150,@attr3:@0.8}],@path:@{ @third:@{@name:@rose,@age:@123}, @3rd:@{@name:@rose,@age:@123}, @sub:@{ @fourth:@{@name:@rose,@age:@123}, @4th:@{@name:@rose,@age:@123}}}};}
資料量並不大,可以看到最外層的資料包含了字串,日期,數字,數組,詞典這些常用的資料類型~KEY:`l`對應的@3456789映射到long類型的資料,`f`映射到float類型的資料,雖然目標是數字類型,但是json中究竟是什麼格式並沒有關係~
而日期方面,可以看出date1使用的是時間戳記,而date2使用的是日期文字~針對於日期資料的處理全部通過NESDateHandler來完成,坦白說這個東西已經可以分出來作為一個獨立的日期工具存在了~寫這東西的時候多少還是走了點心,不過這倒不是今天的重點~貼出這個類的測試案例就差不多包含了它的全部用法了~完整的測試案例和源碼在最後都可以下載
-(void)testBlockHandler{ NSDateFormatter *fmt = [[NSDateFormatter alloc] init]; fmt.dateFormat = @yyyy-MM-dd; NSDate *date1 = [NESDateHandler dateWith:@date:2015-10-10 using:^NSDate *(id object) { NSString *dateString = [[object componentsSeparatedByString:@:] lastObject]; return [fmt dateFromString:dateString]; }]; NSDate *date2 = [fmt dateFromString:@2015-10-10]; XCTAssertEqualObjects(date1, date2);}-(void)testCustomFormatter{ NSDateFormatter *fmt = [[NSDateFormatter alloc] init]; fmt.dateFormat = @yyyy-MM-dd; [NESDateHandler setDateFormatter:fmt]; NSDate *date1 = [NESDateHandler dateWith:@2015-10-10 11:11:11]; NSDate *date2 = [NESDateHandler dateWith:@2015]; //日期文字不匹配,按照時間戳記處理,兩者得到的結果應該相同 XCTAssertEqualObjects(date1, date2); date1 = [NESDateHandler dateWith:@2015-10-10]; date2 = [fmt dateFromString:@2015-10-10]; XCTAssertEqualObjects(date1, date2); [NESDateHandler setDateFormatter:[NESDateHandler defaultDateFormatter]];}-(void)testDefaultDateStringHandler{ NSDate *date = [NSDate dateWithTimeIntervalSince1970:60*60*24*365*45 + 60*60*24 * 11]; //45年+11個閏年+8個時區 NSString *str = @2015-01-01 08:00:00; NSString *target = [NESDateHandler stringWith:date]; XCTAssertEqualObjects(str, target);}-(void)testCustomDateStringHandler{ NSString *str = @自訂日期處理器; [NESDateHandler setDateStringHandler:^NSString *(NSDate * date) { return @自訂日期處理器; }]; NSString *target = [NESDateHandler stringWith:[NSDate date]]; XCTAssertEqualObjects(str, target); [NESDateHandler setDateStringHandler:[NESDateHandler defaultDateStringHandler]]; }-(void)testCustomDateHandler{ [NESDateHandler setDateHandler:^NSDate *(id object) { return [NSDate dateWithTimeIntervalSince1970:60*60*24*365*100]; }]; NSDate *date = [NSDate dateWithTimeIntervalSince1970:60*60*24*365*100]; NSDate *target = [NESDateHandler dateWith:@2015]; XCTAssertNotNil(target); XCTAssertEqualObjects(date, target); [NESDateHandler setDateHandler:[NESDateHandler defaultDateHandler]]; }-(void)testDateFromInvalidateString{ NSDate *date = [NESDateHandler dateWith:@a876yikdvhas]; NSDate *dateNow = [NSDate date]; XCTAssertNotNil(date); NSTimeInterval t = dateNow.timeIntervalSince1970 - date.timeIntervalSince1970; XCTAssert(t > 0 && t < 0.001);}-(void)testDateFromTimeInterval{ NSDate *date1 = [NESDateHandler dateWith:@2015/4/5 22:11:11.123]; NSDate *date2 = [NESDateHandler dateWith:@2015]; XCTAssertNotNil(date1); XCTAssertNotNil(date2); XCTAssertEqualObjects(date1, date2);}-(void)testDateFromValidateString{ NSDate *date1 = [NESDateHandler dateWith:@2014-4-5 22:10:19]; XCTAssertNotNil(date1);}
關於日期文字的處理倒是MJExtension沒有的功能,也是我比較需要的一個東西~接下來就是針對上面的假資料應該如何定義模型了
#import #import NESOtherModel.h#import NESThirdModel.h#import NESFourthModel.h@interface NESDemoModel : NSObject@property (nonatomic,retain) NSString *name;@property (nonatomic,retain) NSString *test_id;@property (nonatomic,retain) NSString *code;@property (nonatomic,retain) NESOtherModel *other;@property (nonatomic,assign) int test;@property (nonatomic,assign) double d;@property (nonatomic,assign) long l;@property (nonatomic,assign) float f;@property (nonatomic,assign) BOOL b;@property (nonatomic,retain) NSDate *date1;@property (nonatomic,retain) NSDate *date2;@property (nonatomic,retain) NSArray *other_arr;@property (nonatomic,retain) NESThirdModel *_path_third;@property (nonatomic,retain) NESThirdModel *_path_3rd;@property (nonatomic,retain) NESFourthModel *_path_sub_fourth;@property (nonatomic,retain) NESFourthModel *_path_sub_4th;@property (nonatomic,assign) NSString * notExists_;@end
沒有任何協議,定義時不需要引入額外的標頭檔,直接繼承自NSObject就好~屬性的命名規則大體如下:
1.直接映射,屬性名稱與json索引值相同
2.間接映射,以字母開頭,以`_`串連,前邊為任一字元串,後邊為json鍵,
例:@property (nonatomic,retain) NSString *test_id;對應json中的id
3.數組映射,對於數群組類型的屬性,用`_`串連模型名稱和json鍵即可完成映射
例:@property (nonatomic,retain) NSArray *other_arr;對應json中的arr,至於用什麼模型進行封裝後文會說明
4.路徑映射,當希望將json內部的某個對象直接映射成為模型屬性的時候使用,以`_`開頭,每一級路徑以`_`串連
以json鍵作為結尾,路徑映射可以包含多級路徑
例:@property (nonatomic,retain) NESFourthModel *_path_sub_fourth;這裡是二級路徑映射,對照上邊的資料來源很容易理解
5.忽略屬性,當需要在模型中定義一個json對象中沒有的屬性,同時在進行json字串轉化時將其忽略的屬性時,直接在屬性名稱最後加`_`
例:@property (nonatomic,assign) NSString * notExists_;這個就是在映射時不做任何操作的欄位了
關於json對象內數組的映射,用到了一個叫做NESModelFormat的類,其中定義了全域的首碼和尾碼,預設分別是Null 字元串和Model,從標頭檔定義中看到引入了一個叫做`NESOtherModel`的類~這裡`NES`就是首碼,需要在NESModelFormat中對齊進行手都配置,當然配置是全域的,只要在適當的位置配置一次就可以了~而這樣`other_arr`的效果就是把json中arr對應的數組映射成為NESOtherModel類的對象組成的數組~實際使用的時候採用什麼首碼什麼尾碼自己配置就是了~
映射完成後的對象反向產生的json字串就是
{b:1,test:3,f:123.123,name:jack,id:123,code:4567890-,other:{attr3:0.8,attr1:value1,attr2:150},d:4.5,l:3456789,date1:2399-12-14 02:27:36,date2:2015-04-03 11:18:24,arr:[{attr3:0.8,attr1:value1,attr2:150},{attr3:0.8,attr1:value1,attr2:150}],path:{third:{age:123,name:rose},3rd:{age:123,name:rose},sub:{fourth:{age:123,name:rose},4th:{age:123,name:rose}}}}
考慮到篇幅,就不加格式了~
而模型對象轉詞典的時候用了比較偷懶的寫法...
[NSJSONSerialization JSONObjectWithData:[self.jsonString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
到這裡所有的功能也就出來了~所有的API定義在NSObject+NESModel.h中~也就三個方法
+(id)mappingWithObject:(id)object;-(NSString *)jsonString;-(id)foundationModel;
用起來大概是這個樣子:
NESDemoModel *model = [NESDemoModel mappingWithObject:self.fakeData];NSArray *arr = [NESDemoModel mappingWithObject:@[self.fakeData,self.fakeData,self.fakeData,self.fakeData]];NSDictionary *dict = arr.foundationModel;
ok~大體就是這些內容了~還有考慮欠妥和功能不夠完善的地方~歡迎大家提提意見,或者有什麼尚未滿足的需求需要添加也可以聯絡我~能力範圍內的會儘快添加進來~
當然,這個畢竟是我個人比較喜歡的方式,簡單總結一下,能夠正確處理NSDate類型的屬性,不需要在實現檔案中寫代碼,屬性明見名知意~如果你也喜歡這種方式歡迎使用這套工具~
如果對運行效率方面要求更高,那麼還是強烈推薦使用MJExtension~