JSON轉Model內部實現解析


一、思路:
1、通過模型類型獲得所有的屬性和其類型
2、對獲得的json進行處理。類型處理
3、考慮字典鍵值和模型屬性名不一致的情況
4、添加code用於歸檔
5、補充JSON轉字典、字典轉JSON、字典轉模型等接口
6、對處理過的properties做緩存
 
二、設計模式思考:
設計模式的選擇---------繼承、接口、抽象基類的選擇。
在使用方便、高效率、低耦合之間抉擇。
 
三、細節及實現
先把任務分解,實現各個部分細節,然后進行組合,當然我們要思考好,采用何種設計模式組裝。先來看看各個部分的實現細節。
 
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模型如下

//屬性}
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;

輸出結果為

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。
 
水平有限,錯誤之處,歡迎大家指正。互相學習。轉載請注明出處


免責聲明!

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



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