作移動端開發,解析網絡數據是必不可少的工做之一。iOS原生框架很早前就已經提供了將JSON數據直接映射成數組或者字典對象的方法,而且結合KVC,也能夠將字典數據直接賦值給對象。可是這種方式十分不靈活,例如若是網絡數據中的字段與咱們數據模型中的字段不一致,某些網絡數據的字段可能爲nil等等都須要開發者單獨的處理。使用JSOMModel能夠十分方便的處理映射過程當中的各類狀況。json
平時在使用JSOMModel框架時,每每只會用到JSOMModel這一個類,其實JSOMModel中還封裝了一套網絡請求邏輯,你能夠直接對某個對象調用請求來映射成爲數據模型。可是我建議儘可能將數據的請求和解析分開來作,這樣更利於請求的維護(在新的JSOMModel版本中,也將有關網絡請求的部分標記爲了棄用)。JSOMModel功能十分強大,代碼量卻並不特別多,結構邏輯也較爲清晰。其中類的關係和結構以下圖表示。數組
如上圖所示,其中網絡相關模塊已經棄用,而且也不是JSONModel的核心模塊,不在本次博客的探討範圍以內。JSONModelError定義了許多錯誤類型,主要用來當請求或數據解析異常時進行拋出,須要注意,JSONModel定義的本身的log函數,其只會在模擬器運行時進行打印。網絡
將網絡數據映射爲Model模型的實質便是對Model對象中屬性的賦值,在JSONModel中,類的屬性被抽象爲JSONModelClassProperty對象,這個對象中封裝這此屬性的相關信息(經過runtime來動態生成)。JSONModelClassProerty類中的屬性意義以下:app
@interface JSONModelClassProperty : NSObject //已經棄用 這個用來標識當前屬性是不是對象的主鍵 用來進行數據模型的比較 @property (assign, nonatomic) BOOL isIndex DEPRECATED_ATTRIBUTE; //屬性名 @property (copy, nonatomic) NSString *name; //屬性類型 @property (assign, nonatomic) Class type; //屬性結構體名稱 基本數據類型的屬性 會被抽象成結構體 @property (strong, nonatomic) NSString *structName; //屬性遵照的協議名 @property (copy, nonatomic) NSString *protocol; //當前屬性是不是可選屬性 若是是 在解析時容許這個屬性值爲nil @property (assign, nonatomic) BOOL isOptional; //是不是標準的json數據,若是是則不用再調用數據轉換的方法 @property (assign, nonatomic) BOOL isStandardJSONType; //當前屬性是不是可變的 若是是 則會建立可變對象 @property (assign, nonatomic) BOOL isMutable; //自定義的 屬性get函數 @property (assign, nonatomic) SEL customGetter; //自定義的屬性 set函數 @property (strong, nonatomic) NSMutableDictionary *customSetters; @end
簡單理解,JSONKeyMapper屬性映射器的做用就是用來制定在數據解析時所遵循的規則。最理想的狀況是JSON數據與咱們要解析成的數據模型徹底對應,例如:框架
JSON數據:函數
{ "firstName":"Bill" , "lastName":"Gates" }
數據Model:atom
@interface MyOnject : NSObject @property(nonatomic,strong)NSString * firstName; @property(nonatomic,strong)NSString * lastName; @end
然而在實際的開發中,這種完美的狀況卻不多出現,咱們更多遇到的是,JSON數據中某些字段可能有也可能無,數據Model中須要增長些本地字段,JSON數據和Model的某些字段名稱可能不一致。更加複雜一點,咱們能夠Model的某個屬性是另外一個Model。或者某個屬性是數組,數組中存放的是另外一種Model。spa
JSONKeyMapper接口定義以下:調試
//經過字典來建立映射器 字典的鍵爲數據Model的屬性名 值爲JSOM數據的屬性名 - (instancetype)initWithModelToJSONDictionary:(NSDictionary *)toJSON; //經過block來創建映射關係 block的定義以下,其中會將JSOM數據的屬性名傳入 須要返回要對應Model的屬性名 /* typedef NSString *(^JSONModelKeyMapBlock)(NSString *keyName); */ - (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON; //建立一個 將如下劃線分割的命名鍵 轉換成駝峯命名 例如 first_name => firstName + (instancetype)mapperForSnakeCase; //建立一個 將首字母大寫的明明鍵 轉換成駝峯 例如 FirstName => firstName + (instancetype)mapperForTitleCase;
JSONModel框架中最核心的類JSONModel類,其中代碼大約有1400行,除了一些調試,複寫和提供方便功能的代碼外,核心代碼在800行左右。首先,其頭文件中聲明瞭幾個協議,以下:code
@protocol Index @end @protocol Ignore @end @protocol Optional @end
須要注意,這些協議裏面都沒有約定任何方法,它們也不會用來實現的,其做爲屬性的一種標記,例如將屬性添加Ignore協議,則JSONModel不會對這個屬性進行解析,使用這種方式來進行本地數據的管理,例如:
@interface MyOnject : JSONModel @property(nonatomic,strong)NSString * firstName; @property(nonatomic,strong)NSString * lastName; //這個屬性是本地拼接 使用 @property(nonatomic,strong)NSString<Ignore> * fullName; @end
Optional協議表示這個屬性是可選的,即JSON數據中若是有這個屬性就解析,若是沒有就跳過。Index協議標記這個屬性是當前對象的主鍵,已經棄用。
有了這3個協議,在聲明屬性時,咱們能夠十分容易的設定他們的解析規則,在JSONModel中,協議除了能夠用來規定解析規則外,還能夠用來指定自定義數據類型的解析,只是咱們須要本身定義一個協議,名稱與自定義類名一致,示例以下:
#import "JSONModel.h" @protocol Address @end @interface Address:JSONModel @property(nonatomic,strong)NSString * info; @end @interface MyObject : JSONModel @property(nonatomic,strong)NSString<Optional> * firstName; @property(nonatomic,strong)NSString * lastName; @property(nonatomic,strong)NSArray<Address> * address; @end
如上代碼因此,在解析數據時,會直接將address數組中賦值爲Address的對象,當前也能夠直接解析對象,例如:
@protocol Address @end @interface Address:JSONModel @property(nonatomic,strong)NSString * info; @end @interface MyObject : JSONModel @property(nonatomic,strong)NSString<Optional> * firstName; @property(nonatomic,strong)NSString * lastName; @property(nonatomic,strong)Address<Address> * address; @end
須要注意,在Objective-C中,只有NSObject的子類能夠遵照協議,原始數據類型是不能遵照協議的,那麼對於相似BOOL,int這樣的屬性有沒有辦法設置他們的忽略解析或者可選解析呢,固然也能夠,咱們能夠經過重寫JSONModel中的一些函數來實現,這種方法更加通用,JSONModel類接口意義以下:
//將JSON字符串解析成數據模型對象 - (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err; - (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err; //將數據模型對象轉換成JSON字符串 - (NSString *)toJSONString; //將數據模型對象轉換成JSON數據 - (NSData *)toJSONData; //將數據模型對象中的某些鍵組合成JSON字符串 - (NSString *)toJSONStringWithKeys:(NSArray *)propertyNames; //將數據模型對象中的某些鍵組合成JSON數據 - (NSData *)toJSONDataWithKeys:(NSArray *)propertyNames; //重寫這個函數 來設置解析時使用的屬性映射器 + (JSONKeyMapper *)keyMapper; //重寫這個函數 來設置某個屬性是不是可選的 + (BOOL)propertyIsOptional:(NSString *)propertyName; //重寫這個函數 來設置某個屬性是不是忽略的 + (BOOL)propertyIsIgnored:(NSString *)propertyName; //重寫這個函數 來設置 若是某個屬性集合中是一個自定義對象或自己是自定義對象 設置此對象的類 + (Class)classForCollectionProperty:(NSString *)propertyName;
JSONModel的源碼這裏就不在列舉,其首先在類load函數中進行靜態數據的加載,所支持的原生類型和基礎數據類型的定義等。在對象的初始化方法中,首先使用runtime獲取全部的屬性和屬性的修飾內容,所謂修飾內容,便是指屬性名稱,類型,所遵照的協議,以及是否忽略,是否可選,是不是主鍵等內容(過程當中會使用到屬性映射器keyMapper進行轉化),將其抽象成JSONModelClassProperty對象。後面在解析時,會根據JSONModelClassProperty中的自定義setter和其餘信息進行賦值。