原文鏈接:http://www.jianshu.com/p/25e678fa43d3
demo鏈接:https://github.com/walkertop/YYModel---Demo
插件鏈接:https://github.com/EnjoySR/ESJsonFormat-Xcode
開篇說明:
雖然網上有很多講解YYModel使用方法的文章,包括YYModel作者也在github上對其做了使用說明。
但在我實際使用過程中,依然發現文檔的不完善,比如對於復雜的模型(如多層嵌套)講解的仍不透徹,同時本文也會介紹一神器配合YYModel使用,讓你感受分分鍾搞定模型創建的酸爽。
當然為了減少讀者的學習成本,本會對YYModel作者的文檔進行豐富和擴展。
可在github上下載Demo,以便更直觀了解各種使用場景詳細代碼。
文章只要包含:
- 詳解YYModel的多種使用場景
- 拓展插件,讓你一分鍾搞定所有的模型的創建和調用。
一、YYModel的使用場景
1.簡單的 Model 與 JSON 相互轉換
// JSON:
{
"uid":123456, "name":"Harry", "created":"1965-07-31T00:00:00+0000" } // Model: @interface User : NSObject @property UInt64 uid; @property NSString *name; @property NSDate *created; @end @implementation User @end
// 將 JSON (NSData,NSString,NSDictionary) 轉換為 Model:
User *user = [User yy_modelWithJSON:json]; // 將 Model 轉換為 JSON 對象: NSDictionary *json = [user yy_modelToJSONObject];
JSON/Dictionary 中的對象類型與 Model 屬性不一致時,YYModel 將會進行如下自動轉換。自動轉換不支持的值將會被忽略,以避免各種潛在的崩潰問題。

2.Model 屬性名和 JSON 中的 Key 不相同
// JSON: { "n":"Harry Pottery", "p": 256, "ext" : { "desc" : "A book written by J.K.Rowing." }, "ID" : 100010 } // Model: @interface Book : NSObject @property NSString *name; @property NSInteger page; @property NSString *desc; @property NSString *bookID; @end @implementation Book //返回一個 Dict,將 Model 屬性名對映射到 JSON 的 Key。 + (NSDictionary *)modelCustomPropertyMapper { return @{@"name" : @"n", @"page" : @"p", @"desc" : @"ext.desc", @"bookID" : @[@"id",@"ID",@"book_id"]}; } @end
你可以把一個或一組 json key (key path) 映射到一個或多個屬性。如果一個屬性沒有映射關系,那默認會使用相同屬性名作為映射。
在 json->model 的過程中:如果一個屬性對應了多個 json key,那么轉換過程會按順序查找,並使用第一個不為空的值。
在 model->json 的過程中:如果一個屬性對應了多個 json key (key path),那么轉換過程僅會處理第一個 json key (key path);如果多個屬性對應了同一個 json key,則轉換過過程會使用其中任意一個不為空的值。
3.Model 包含其他 Model
// JSON { "author":{ "name":"J.K.Rowling", "birthday":"1965-07-31T00:00:00+0000" }, "name":"Harry Potter", "pages":256 } // Model: 什么都不用做,轉換會自動完成 @interface Author : NSObject @property NSString *name; @property NSDate *birthday; @end @implementation Author @end @interface Book : NSObject @property NSString *name; @property NSUInteger pages; @property Author *author; //Book 包含 Author 屬性 @end @implementation Book @end
4.容器類屬性
@class Shadow, Border, Attachment; @interface Attributes @property NSString *name; @property NSArray *shadows; //Array<Shadow> @property NSSet *borders; //Set<Border> @property NSMutableDictionary *attachments; //Dict<NSString,Attachment> @end @implementation Attributes // 返回容器類中的所需要存放的數據類型 (以 Class 或 Class Name 的形式)。 + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"shadows" : [Shadow class], @"borders" : Border.class, @"attachments" : @"Attachment" }; } @end
在實際使用過過程中,[Shadow class]
,Border.class
,@"Attachment"
沒有明顯的區別。
這里僅僅是創建作者有說明,實際使用時,需要對其遍歷,取出容器中得字典,然后繼續字典轉模型。(YYModel的核心是通過runtime獲取結構體中得Ivars的值,將此值定義為key,然后給key賦value值,所以我們需要自己遍歷容器(NSArray,NSSet,NSDictionary),獲取每一個值,然后KVC)。
- 具體的代碼實現如下:
NSDictionary *json =[self getJsonWithJsonName:@"ContainerModel"]; ContainerModel *containModel = [ContainerModel yy_modelWithDictionary:json]; NSDictionary *dataDict = [containModel valueForKey:@"data"]; //定義數組,接受key為list的數組 self.listArray = [dataDict valueForKey:@"list"]; //遍歷數組 [self.listArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSDictionary *listDict = obj; //獲取數組中得字典 List *listModel = [List yy_modelWithDictionary:listDict]; //獲取count 和 id NSString *count = [listModel valueForKey:@"count"]; NSString *id = [listModel valueForKey:@"id"];
5.黑名單與白名單
@interface User @property NSString *name; @property NSUInteger age; @end @implementation Attributes // 如果實現了該方法,則處理過程中會忽略該列表內的所有屬性 + (NSArray *)modelPropertyBlacklist { return @[@"test1", @"test2"]; } // 如果實現了該方法,則處理過程中不會處理該列表外的屬性。 + (NSArray *)modelPropertyWhitelist { return @[@"name"]; } @end
6.數據校驗與自定義轉換
實際這個分類的目的比較簡單和明確。
就是對判斷是否為時間戳,然后對時間戳進行處理,調用_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
獲取時間。
// JSON: { "name":"Harry", "timestamp" : 1445534567 //時間戳 } // Model: @interface User @property NSString *name; @property NSDate *createdAt; @end @implementation User // 當 JSON 轉為 Model 完成后,該方法會被調用。 // 你可以在這里對數據進行校驗,如果校驗不通過,可以返回 NO,則該 Model 會被忽略。 // 你也可以在這里做一些自動轉換不能完成的工作。 - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { NSNumber *timestamp = dic[@"timestamp"]; if (![timestamp isKindOfClass:[NSNumber class]]) return NO; _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; return YES; } // 當 Model 轉為 JSON 完成后,該方法會被調用。 // 你可以在這里對數據進行校驗,如果校驗不通過,可以返回 NO,則該 Model 會被忽略。 // 你也可以在這里做一些自動轉換不能完成的工作。 - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { if (!_createdAt) return NO; dic[@"timestamp"] = @(n.timeIntervalSince1970); return YES; } @end
- 需要注意的時,如果用插件,對時間戳類型或默認創建為NSUInteger類型,需要將其更改為NSDate類型。
7.Coding/Copying/hash/equal/description
以下方法都是YYModel的簡單封裝,實際使用過程和系統方法區別不大。對其感興趣的可以點進方法內部查看。
@interface YYShadow :NSObject <NSCoding, NSCopying> @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) CGSize size; @end @implementation YYShadow // 直接添加以下代碼即可自動完成 - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; } - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } - (NSUInteger)hash { return [self yy_modelHash]; } - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; } - (NSString *)description { return [self yy_modelDescription]; } @end
-
二、ESJsonFormat與YYModel的結合使用
彩蛋
給大家介紹一款插件,配合ESJsonFormat
配圖:

使用方法:
快捷鍵:shift + control + J
插件安裝方法比較簡單,在此不贅述,不知道可自行google。
好處:
- 可以直接將json數據復制,ESJsonFormat會根據數據類型自動生成屬性。(建議還是要自行檢查,比如時間戳,系統會默認幫你生成為NSUInteger,而我們想要的為NSDate類型)
- 對於多模型嵌套,不必創建多個文件,ESJsonFormat會自動在一個文件下創建多重類型,極其便捷。
至此YYModel的使用已講解完畢,關於YYModel的底層核心是運用runtime獲取類結構體中Ivars,進行KVC操作,然后根據不同情況進行分別處理
。
#import "YYModel.h"
使用YYModel把字典數組轉模型數組的核心代碼
- 參數1:要轉成的模型類型
- 參數2:字典數組
- 結果:模型數組
self.modelArray = [NSArray yy_modelArrayWithClass:[NewsModel class] json:dictArr];
使用YYModel把字典數組轉模型數組的完整邏輯
- (void)loadData { // URL NSURL *url = [NSURL URLWithString:@"http://news.coolban.com/Api/Index/news_list/app/2/cat/0/limit/20/time/1457168894/type/0?channel=appstore&uuid=19C2BF6A-94F8-4503-8394-2DCD07C36A8F&net=5&model=iPhone&ver=1.0.5"]; // 發起任務獲取JSON數據 [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 反序列化JSON == 字典數組 NSArray *dictArr = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; // YYModel實現字典數組轉模型數組 self.modelArray = [NSArray yy_modelArrayWithClass:[NewsModel class] json:dictArr]; // 回到主線程刷新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // [self.tableView reloadData]; }]; }] resume];