JSON轉Model內部實現解析

1、思路:
一、經過模型類型得到全部的屬性和其類型
二、對得到的json進行處理。類型處理
三、考慮字典鍵值和模型屬性名不一致的狀況
四、添加code用於歸檔
五、補充JSON轉字典、字典轉JSON、字典轉模型等接口
六、對處理過的properties作緩存
 
2、設計模式思考:
設計模式的選擇---------繼承、接口、抽象基類的選擇。
在使用方便、高效率、低耦合之間抉擇。
 
3、細節及實現
先把任務分解,實現各個部分細節,而後進行組合,固然咱們要思考好,採用何種設計模式組裝。先來看看各個部分的實現細節。
 
1.經過模型類型得到全部的屬性和其類型,
   
 unsigned int outCount = 0;
     //得到Class c全部屬性這裏的c是[Model class]
    objc_property_t *properties = class_copyPropertyList(c, &outCount);
    
    for (int i = 0; i < outCount; i++) {
        objc_property_t propert = properties[i];
       //得到屬性名
        NSString *key = @(property_getName(propert));
        //得到屬性類型,如CGFloat、nonatomic、copy等信息
        NSString *type = @(property_getAttributes(propert));
        NSLog(@"key = %@ , type = %@", key, type);
     }

 Model模型以下json

//屬性}
typedef void(^block)();
@interface Model : NSObject
@property (nonatomic, copy) NSString *q_NSString;
@property (nonatomic, assign) CGFloat q_CGFloat;
@property (nonatomic, assign) CGRect q_CGRect;
@property (nonatomic, assign) double q_double;
@property (nonatomic, assign) int q_int;
@property (nonatomic, assign) BOOL q_bool;
@property (nonatomic, assign) float q_float;
@property (nonatomic, assign) short q_short;
@property (nonatomic, assign) long q_long;
@property (nonatomic, assign) long long q_longlong;
@property (nonatomic, assign) Point q_point;

@property (nonatomic, strong) id q_id;
@property (nonatomic, weak) id<NSObject> q_delegate;
@property (nonatomic, copy) block q_block;
@property (nonatomic, strong) Model1 *q_model1;
 
@property SEL q_SEL;
@property Class q_Class;
@property Ivar q_Ivar;
@property Method q_Method;

輸出結果爲swift

key = q_NSString , type = T@"NSString",C,N,V_q_NSString
key = q_CGFloat , type = Td,N,V_q_CGFloat
key = q_CGRect , type = T{CGRect={CGPoint=dd}{CGSize=dd}},N,V_q_CGRect
key = q_double , type = Td,N,V_q_double
key = q_int , type = Ti,N,V_q_int
key = q_bool , type = TB,N,V_q_bool
key = q_float , type = Tf,N,V_q_float
key = q_short , type = Ts,N,V_q_short
key = q_long , type = Tq,N,V_q_long
key = q_longlong , type = Tq,N,V_q_longlong
key = q_point , type = T{Point=ss},N,V_q_point
key = q_id , type = T@,&,N,V_q_id
key = q_delegate , type = T@"<NSObject>",W,N,V_q_delegate
key = q_block , type = T@?,C,N,V_q_block
key = q_model1 , type = T@"Model1",&,N,V_q_model1
key = q_SEL , type = T:,V_q_SEL
key = q_Class , type = T#,&,V_q_Class
key = q_Ivar , type = T^{objc_ivar=},V_q_Ivar
key = q_Method , type = T^{objc_method=},V_q_Method

 

將type用」,」分開, T@"NSNumber",N,R,Vname  爲例
在類中的聲明爲 let name: NSNumber =  NSNumber()
  • T@「NSNumber」 標記了屬於什麼類型
  • N            線程安全 至關與Objective-C中的nonmatic
  • R            不可變,R至關與Objective-C中的readonly,C至關於copy        
  • Vname        去掉V,name就是變量名
經過對type進行處理就能夠得到屬性的類型。從而進行下一步處理。
注意點:class_copyPropertyList返回的僅僅是對象類的屬性,class_copyIvarList返回類的全部屬性和變量,在swift中如let a: Int? 是沒法經過class_copyPropertyList返回的。
 
2.對type的處理使用方法可能不一樣,可是目的是同樣的,就是爲了從type字符串中區分出不一樣的類型
主要分爲這幾種:對象、協議、block、基本類型、結構體、自定義類型、Class、Ivar、Method、SEL、
 
MJExtension採用這樣區分,在MJExtensionConst中這樣定義
NSString *const MJPropertyTypeInt = @"i";
NSString *const MJPropertyTypeShort = @"s";
NSString *const MJPropertyTypeFloat = @"f";
NSString *const MJPropertyTypeDouble = @"d";
NSString *const MJPropertyTypeLong = @"l";
NSString *const MJPropertyTypeLongLong = @"q";
NSString *const MJPropertyTypeChar = @"c";
NSString *const MJPropertyTypeBOOL1 = @"c";
NSString *const MJPropertyTypeBOOL2 = @"b";
NSString *const MJPropertyTypePointer = @"*";

NSString *const MJPropertyTypeIvar = @"^{objc_ivar=}";
NSString *const MJPropertyTypeMethod = @"^{objc_method=}";
NSString *const MJPropertyTypeBlock = @"@?";
NSString *const MJPropertyTypeClass = @"#";
NSString *const MJPropertyTypeSEL = @":";
NSString *const MJPropertyTypeId = @"@";
MJExtension採用對type進行字符串處理就可以區分出具體的類型
而在JSONModel採用 NSScanner,對類型進行處理。
比較下,我的以爲採用定義MJExtensionConst這樣一個類來存放區分的值顯得更加清晰。對於閱讀源代碼的人來講相對比較好。固然也能夠結合起來使用
 
3.對得到的JSON進行類型處理。

  • 在JSON中爲NSNumer,而propertytype爲NSString,這種狀況很常見。咱們就須要處理一下,當propertytype爲NSString,而在JSON中爲NSNumber,就把NSNumber轉化爲NSString。
  • Readonly不須要賦值
  • nil處理
  • 可變和不可變處理
  • 模型就須要遞歸處理
  • NSString -> NSURL
  • 字符串轉BOOL
  • 還有一些其餘處理,以上的處理中也不是每一個第三方都進行處理了
截取MJEextension中的一部分代碼
        
  if ([value isKindOfClass:[NSStringclass]]) {
                    if (propertyClass == [NSURL class]) {
                        // NSString -> NSURL
                        // 字符串轉碼
                        value = [value mj_url];
                    } else if (type.isNumberType) {
                        NSString *oldValue = value;
                       
                        // NSString -> NSNumber
                        value = [numberFormatter_ numberFromString:oldValue];
                       
                        // 若是是BOOL
                        if (type.isBoolType) {
                            // 字符串轉BOOL(字符串沒有charValue方法)
                            // 系統會調用字符串的charValue轉爲BOOL類型
                            NSString *lower = [oldValue lowercaseString];
                            if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                                value = @YES;
                            } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                                value = @NO;
                            }
                        }
                    }
                }

 

4.採用KVC賦值
setValue:forKey:
 就很少說了
 
5.考慮字典鍵值和模型屬性名不一致的狀況
好比id、descripition不能做爲屬性名,當服務器使用時咱們就須要另外構造名字;還有服務器通常使用user_name的命名方式,而OC中通常使用駝峯命名法即userName,那麼咱們就須要創建一對一的對應關係。
 
我想到有三種方法處理:
  •      採用繼承,定義model基類,子類重寫父類方法。
  •      block設置block回調。
  •      能夠採用抽象基類。
      這個部分就須要咱們好好考慮下了,由於涉及到咱們如何提供接口,使用者如何使用的問題。須要考慮到使用是否方便、效率高低、低耦合是否高等問題。
     先來講第一種,採用繼承的方法,無疑的會帶來高耦合的後果,固然在設計合理的狀況下,大部分狀況下使用起來更方便。
     第二種採用block的方法。解決了繼承帶來的高耦合的成本。使用起來也很方便。MJEextension就是用這種方法。可是這種方法每每讓咱們在調用轉化方法時調用block,些一大串的不一樣鍵值的轉化。我的以爲不一樣鍵值的轉化寫在model裏面比較好,並且在封裝時,就有意識地引導使用者這麼作
  第三種方法使用抽象基類,定義一個協議,協議提供一個或者幾個鍵值轉化的方法。當model遵照並實現了此協議,就找處處理方法進行處理。這種做法避免了繼承的高耦合的成本,也是使用者在model遵照協議,實現此方法。鍵值轉化的方法寫在model裏面,避免轉化的地方些太多東西。有時候還要寫幾遍。固然這樣也有些缺點,就是每次有須要轉化時(並且鍵值不一樣的狀況很常見)須要引入,而後遵照協議。連續建幾個model時,複製起來都不方便。
 
6.其餘方面
    (未測試) 對於SEL、Method 、Ivar不能使用KVC,若是考慮這些狀況,須要進行判斷。
    在賦值時,設置能夠忽略的屬性
 
7.添加code用於歸檔
  添加方法
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
 
通常是給NSObject添加分類,由於咱們能夠獲得類的全部屬性和對應的值。在兩個方法中遍歷屬性,進行編碼和解碼。
方便作些緩存,不少的頁面緩存就能夠這樣作。使用比較方便。
 
8.補充JSON轉字典
+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
  if (jsonString == nil) {
    return nil;
  }
  NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
  NSError *error;
  NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData        
                                                     options:NSJSONReadingMutableContainers
                                                       error:&error];
  if(error) {
    NSLog(@"json解析失敗:%@",error);
    return nil;
  }
  return dic;
}
 
此方法稍做變換,也可輸出數組,主要看json格式。
 
9.字典轉JSON
+ (NSString*)dictionaryToJson:(NSDictionary *)dic
{
    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic 
                                 options:NSJSONWritingPrettyPrinted
                                 error:&error]; return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; }

 

10.對處理過的properties通常須要作緩存
     定義一個字典緩存,緩存處理過的properties,重複使用是不須要重複計算,用空間換時間。例如在歸檔的時候須要得到屬性名列表,而後遍歷歸檔、解擋。其餘中間操做也須要用到properties。
 
水平有限,錯誤之處,歡迎你們指正。互相學習。轉載請註明出處
相關文章
相關標籤/搜索