├── ContainerModel.h ├── ContainerModel.m ├── README.md ├── YYModel-Demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── guobin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── guobin.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── YYModel-Demo.xcscheme │ └── xcschememanagement.plist └── YYModel-Demo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Author.h ├── Author.m ├── BGTableViewController.h ├── BGTableViewController.m ├── Base.lproj └── LaunchScreen.storyboard ├── BlacklistAndWhitelist.h ├── BlacklistAndWhitelist.json ├── BlacklistAndWhitelist.m ├── Book.h ├── Book.m ├── ContainerModel.json ├── DemoModel.h ├── DemoModel.m ├── DifferentJSONKey.h ├── DifferentJSONKey.json ├── DifferentJSONKey.m ├── DoubleModel.json ├── Info.plist ├── Main.storyboard ├── SimpleModel.json ├── TimeStampModel.h ├── TimeStampModel.m ├── User.h ├── User.m ├── YYModel ├── NSObject+YYModel.h ├── NSObject+YYModel.m ├── YYClassInfo.h ├── YYClassInfo.m └── YYModel.h ├── main.m └── timestamp.json /ContainerModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContainerModel.h 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/22. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class Data,Latest_Expire_Bonus,List; 12 | @interface ContainerModel : NSObject 13 | 14 | @property (nonatomic, assign) NSInteger errnoInteger; 15 | 16 | @property (nonatomic, strong) Data *data; 17 | 18 | @property (nonatomic, copy) NSString *error; 19 | 20 | @end 21 | @interface Data : NSObject 22 | 23 | @property (nonatomic, copy) NSString *count; 24 | 25 | @property (nonatomic, strong) Latest_Expire_Bonus *latest_expire_bonus; 26 | 27 | @property (nonatomic, strong) NSArray *list; 28 | 29 | @end 30 | 31 | @interface Latest_Expire_Bonus : NSObject 32 | 33 | @property (nonatomic, copy) NSString *minutes; 34 | 35 | @property (nonatomic, copy) NSString *money; 36 | 37 | @end 38 | 39 | @interface List : NSObject 40 | 41 | @property (nonatomic, copy) NSString *id; 42 | 43 | @property (nonatomic, copy) NSString *expiredAt; 44 | 45 | @property (nonatomic, copy) NSString *count; 46 | 47 | @property (nonatomic, assign) NSInteger flag; 48 | 49 | @property (nonatomic, assign) NSInteger leftNum; 50 | 51 | @property (nonatomic, copy) NSString *usedNum; 52 | 53 | @property (nonatomic, copy) NSString *createdAt; 54 | 55 | @property (nonatomic, copy) NSString *sendNum; 56 | 57 | @end 58 | 59 | -------------------------------------------------------------------------------- /ContainerModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContainerModel.m 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/22. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "ContainerModel.h" 10 | 11 | @implementation ContainerModel 12 | + (NSDictionary *) modelCustomPropertyMapper { 13 | return @{@"errnoInteger" : @"errno" 14 | }; 15 | } 16 | 17 | @end 18 | @implementation Data 19 | 20 | + (NSDictionary *)objectClassInArray{ 21 | return @{@"list" : [List class]}; 22 | } 23 | 24 | @end 25 | 26 | 27 | @implementation Latest_Expire_Bonus 28 | 29 | @end 30 | 31 | 32 | @implementation List 33 | 34 | @end 35 | 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YYModel---Demo 2 | 丰富的例子展示了怎么使用YYModel 3 | **开篇说明:** 4 | 虽然网上有很多讲解YYModel使用方法的文章,包括YYModel作者也在github上对其做了使用说明。 5 | 但在我实际使用过程中,依然发现文档的不完善,比如对于复杂的模型(如多层嵌套)讲解的仍不透彻,同时本文也会介绍一神器配合YYModel使用,让你感受分分钟搞定模型创建的酸爽。 6 | 当然为了减少读者的学习成本,本会对YYModel作者的文档进行丰富和扩展。 7 | 可在github上下载[Demo](https://github.com/walkertop/YYModel---Demo),以便更直观了解各种使用场景详细代码。 8 | 文章只要包含: 9 | > - 1. 详解YYModel的多种使用场景 10 | > - 2. 拓展插件,让你一分钟搞定所有的模型的创建和调用。 11 | -------- 12 | ## 一、YYModel的使用场景 13 | ### 1.简单的 Model 与 JSON 相互转换 14 | 15 | ``` 16 | // JSON: 17 | { 18 |     "uid":123456, 19 |     "name":"Harry", 20 |     "created":"1965-07-31T00:00:00+0000" 21 | } 22 | 23 | // Model: 24 | @interface User : NSObject 25 | @property UInt64 uid; 26 | @property NSString *name; 27 | @property NSDate *created; 28 | @end 29 | 30 | @implementation User 31 | 32 | @end 33 | ``` 34 | -------- 35 | ``` 36 | // 将 JSON (NSData,NSString,NSDictionary) 转换为 Model: 37 | User *user = [User yy_modelWithJSON:json]; 38 | 39 | // 将 Model 转换为 JSON 对象: 40 | NSDictionary *json = [user yy_modelToJSONObject]; 41 | ``` 42 | 43 | JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。 44 | ![格式自动转换.png](http://upload-images.jianshu.io/upload_images/1467716-33ae643a0ca314fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 45 | -------- 46 | 47 | #### 2.Model 属性名和 JSON 中的 Key 不相同 48 | 49 | ``` 50 | // JSON: 51 | { 52 |     "n":"Harry Pottery", 53 |     "p": 256, 54 |     "ext" : { 55 |         "desc" : "A book written by J.K.Rowing." 56 |     }, 57 |     "ID" : 100010 58 | } 59 | 60 | // Model: 61 | @interface Book : NSObject 62 | @property NSString *name; 63 | @property NSInteger page; 64 | @property NSString *desc; 65 | @property NSString *bookID; 66 | @end 67 | @implementation Book 68 | //返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。 69 | + (NSDictionary *)modelCustomPropertyMapper { 70 |     return @{@"name" : @"n", 71 |              @"page" : @"p", 72 |              @"desc" : @"ext.desc", 73 |              @"bookID" : @[@"id",@"ID",@"book_id"]}; 74 | } 75 | @end 76 | ``` 77 | 你可以把一个或一组 json key (key path) 映射到一个或多个属性。如果一个属性没有映射关系,那默认会使用相同属性名作为映射。 78 | 在 json->model 的过程中:如果一个属性对应了多个 json key,那么转换过程会按顺序查找,并使用第一个不为空的值。 79 | 在 model->json 的过程中:如果一个属性对应了多个 json key (key path),那么转换过程仅会处理第一个 json key (key path);如果多个属性对应了同一个 json key,则转换过过程会使用其中任意一个不为空的值。 80 | -------- 81 | 82 | ### 3.Model 包含其他 Model 83 | 84 | ``` 85 | // JSON 86 | { 87 |     "author":{ 88 |         "name":"J.K.Rowling", 89 |         "birthday":"1965-07-31T00:00:00+0000" 90 |     }, 91 |     "name":"Harry Potter", 92 |     "pages":256 93 | } 94 | 95 | // Model: 什么都不用做,转换会自动完成 96 | @interface Author : NSObject 97 | @property NSString *name; 98 | @property NSDate *birthday; 99 | @end 100 | @implementation Author 101 | @end 102 | 103 | @interface Book : NSObject 104 | @property NSString *name; 105 | @property NSUInteger pages; 106 | @property Author *author; //Book 包含 Author 属性 107 | @end 108 | @implementation Book 109 | @end 110 | ``` 111 | -------- 112 | 113 | ### 4.容器类属性 114 | 115 | ``` 116 | @class Shadow, Border, Attachment; 117 | 118 | @interface Attributes 119 | @property NSString *name; 120 | @property NSArray *shadows; //Array 121 | @property NSSet *borders; //Set 122 | @property NSMutableDictionary *attachments; //Dict 123 | @end 124 | 125 | @implementation Attributes 126 | // 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。 127 | + (NSDictionary *)modelContainerPropertyGenericClass { 128 |     return @{@"shadows" : [Shadow class], 129 |              @"borders" : Border.class, 130 |              @"attachments" : @"Attachment" }; 131 | } 132 | @end 133 | ``` 134 | 在实际使用过过程中,`[Shadow class]`,`Border.class`,`@"Attachment"`没有明显的区别。 135 | 这里仅仅是创建作者有说明,实际使用时,需要对其遍历,取出容器中得字典,然后继续字典转模型。(****YYModel****的核心是通过****runtime****获取结构体中得****Ivars****的值,将此值定义为****key,****然后给****key****赋****value****值,所以我们需要自己遍历容器(****NSArray****,****NSSet****,****NSDictionary****),获取每一个值,然后****KVC****)。 136 | 137 | 138 | -------- 139 | 140 | - 具体的代码实现如下: 141 | 142 | ``` 143 | NSDictionary *json =[self getJsonWithJsonName:@"ContainerModel"]; 144 | ContainerModel *containModel = [ContainerModel yy_modelWithDictionary:json]; 145 | NSDictionary *dataDict = [containModel valueForKey:@"data"]; 146 | //定义数组,接受key为list的数组 147 | self.listArray = [dataDict valueForKey:@"list"];  148 |  //遍历数组 149 | [self.listArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 150 |         NSDictionary *listDict = obj; 151 |         //获取数组中得字典 152 |         List *listModel = [List yy_modelWithDictionary:listDict]; 153 |         //获取count 和 id 154 |         NSString *count = [listModel valueForKey:@"count"]; 155 |         NSString *id = [listModel valueForKey:@"id"]; 156 |         157 | ``` 158 | -------- 159 | 160 | ### 5.黑名单与白名单 161 | 162 | ``` 163 | @interface User 164 | @property NSString *name; 165 | @property NSUInteger age; 166 | @end 167 | 168 | @implementation Attributes 169 | // 如果实现了该方法,则处理过程中会忽略该列表内的所有属性 170 | + (NSArray *)modelPropertyBlacklist { 171 |     return @[@"test1", @"test2"]; 172 | } 173 | // 如果实现了该方法,则处理过程中不会处理该列表外的属性。 174 | + (NSArray *)modelPropertyWhitelist { 175 |     return @[@"name"]; 176 | } 177 | @end 178 | ``` 179 | 180 | -------- 181 | 182 | ### 6.数据校验与自定义转换 183 | 实际这个分类的目的比较简单和明确。 184 | 就是对判断是否为时间戳,然后对时间戳进行处理,调用 185 | `_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];` 186 | 获取时间。 187 | 188 | ``` 189 | // JSON: 190 | { 191 |     "name":"Harry", 192 |     "timestamp" : 1445534567     //时间戳 193 | } 194 | 195 | // Model: 196 | @interface User 197 | @property NSString *name; 198 | @property NSDate *createdAt; 199 | @end 200 | 201 | @implementation User 202 | // 当 JSON 转为 Model 完成后,该方法会被调用。 203 | // 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。 204 | // 你也可以在这里做一些自动转换不能完成的工作。 205 | - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { 206 |     NSNumber *timestamp = dic[@"timestamp"]; 207 |     if (![timestamp isKindOfClass:[NSNumber class]]) return NO; 208 |     _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; 209 |     return YES; 210 | } 211 | 212 | // 当 Model 转为 JSON 完成后,该方法会被调用。 213 | // 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。 214 | // 你也可以在这里做一些自动转换不能完成的工作。 215 | - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { 216 |     if (!_createdAt) return NO; 217 |     dic[@"timestamp"] = @(n.timeIntervalSince1970); 218 |     return YES; 219 | } 220 | @end 221 | ``` 222 | 223 | > - 需要注意的时,如果用插件,对时间戳类型或默认创建为NSUInteger类型,需要将其更改为NSDate类型。 224 | 225 | -------- 226 | 227 | ### 7.Coding/Copying/hash/equal/description 228 | 以下方法都是YYModel的简单封装,实际使用过程和系统方法区别不大。对其感兴趣的可以点进方法内部查看。 229 | 230 | ``` 231 | @interface YYShadow :NSObject 232 | @property (nonatomic, copy) NSString *name; 233 | @property (nonatomic, assign) CGSize size; 234 | @end 235 | 236 | @implementation YYShadow 237 | // 直接添加以下代码即可自动完成 238 | - (void)encodeWithCoder:(NSCoder *)aCoder {  239 | [self yy_modelEncodeWithCoder:aCoder];  240 | } 241 | - (id)initWithCoder:(NSCoder *)aDecoder { 242 | self = [super init]; 243 | return [self yy_modelInitWithCoder:aDecoder];  244 | } 245 | - (id)copyWithZone:(NSZone *)zone {  246 | return [self yy_modelCopy];  247 | } 248 | - (NSUInteger)hash {  249 | return [self yy_modelHash];  250 | } 251 | - (BOOL)isEqual:(id)object {  252 | return [self yy_modelIsEqual:object];  253 | } 254 | - (NSString *)description {  255 | return [self yy_modelDescription];  256 | } 257 | @end 258 | ``` 259 | -------- 260 | 261 | 262 | ## 二、ESJsonFormat与YYModel的结合使用 263 | **彩蛋** 264 | 给大家介绍一款插件,配合[ESJsonFormat](https://github.com/EnjoySR/ESJsonFormat-Xcode) 265 | 266 | 267 | 268 | 配图: 269 | ![ESJsonFormat插件使用.gif](http://upload-images.jianshu.io/upload_images/1467716-5c961bc376c72984.gif?imageMogr2/auto-orient/strip) 270 | 271 | 272 | 使用方法: 273 | 快捷键:`shift + control + J ` 274 | 插件安装方法比较简单,在此不赘述,不知道可自行google。 275 | 276 | **好处**: 277 | > - 1. 可以直接将json数据复制,ESJsonFormat会根据数据类型自动生成属性。(建议还是要自行检查,比如时间戳,系统会默认帮你生成为NSUInteger,而我们想要的为NSDate类型) 278 | > - 2. 对于多模型嵌套,不必创建多个文件,ESJsonFormat会自动在一个文件下创建多重类型,极其便捷。 279 | 280 | 281 | 至此YYModel的使用已讲解完毕,关于YYModel的底层核心是`运用runtime获取类结构体中Ivars,进行KVC操作,然后根据不同情况进行分别处理`。 282 | 此处只是传递给大家一个概念,不展开讲解,网上有很多源码分析文章,可自学google学习。 283 | 文末,做个综述。 284 | 建议大家有时间一定要多看底层,分析源码。不要只会用,知其然不知其所以然。 285 | 如有错误欢迎指出。 286 | 287 | ##写在最后 288 | 我得写作原则: 289 | 在技术学习道路上,阅读量和代码量绝不能线性提升你的技术水平。 290 | 同样写文章也是如此,作者所写的文章完全是基于自己对技术的理解,在写作时也力求形象不抽象。绝不copy充数,所以也欢迎大家关注和参与讨论。 291 | 技术学习绝不能孤胆英雄独闯天涯,而应在一群人的交流碰撞,享受智慧火花的狂欢。 292 | 希望我的文章能成为你的盛宴,也渴望你的建议能成为我的大餐。 293 | 如有错误请留言指正,对文章感兴趣可以关注作者不定期更新,也可微信`bin5211bin`。 294 | -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A60645C01D20CFB400EFA8E6 /* timestamp.json in Resources */ = {isa = PBXBuildFile; fileRef = A60645BF1D20CFB400EFA8E6 /* timestamp.json */; }; 11 | A60645C31D20D01400EFA8E6 /* TimeStampModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A60645C21D20D01400EFA8E6 /* TimeStampModel.m */; }; 12 | A60645C61D20DAE500EFA8E6 /* DemoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A60645C51D20DAE500EFA8E6 /* DemoModel.m */; }; 13 | A6498DD81D1A403C00ADD9E1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DD71D1A403C00ADD9E1 /* main.m */; }; 14 | A6498DDB1D1A403C00ADD9E1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DDA1D1A403C00ADD9E1 /* AppDelegate.m */; }; 15 | A6498DE31D1A403C00ADD9E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A6498DE21D1A403C00ADD9E1 /* Assets.xcassets */; }; 16 | A6498DE61D1A403C00ADD9E1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A6498DE41D1A403C00ADD9E1 /* LaunchScreen.storyboard */; }; 17 | A6498DEF1D1A407800ADD9E1 /* BGTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DEE1D1A407800ADD9E1 /* BGTableViewController.m */; }; 18 | A6498E071D1A40CC00ADD9E1 /* Author.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DF11D1A40CC00ADD9E1 /* Author.m */; }; 19 | A6498E081D1A40CC00ADD9E1 /* BlacklistAndWhitelist.json in Resources */ = {isa = PBXBuildFile; fileRef = A6498DF31D1A40CC00ADD9E1 /* BlacklistAndWhitelist.json */; }; 20 | A6498E091D1A40CC00ADD9E1 /* BlacklistAndWhitelist.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DF41D1A40CC00ADD9E1 /* BlacklistAndWhitelist.m */; }; 21 | A6498E0A1D1A40CC00ADD9E1 /* Book.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DF61D1A40CC00ADD9E1 /* Book.m */; }; 22 | A6498E0B1D1A40CC00ADD9E1 /* ContainerModel.json in Resources */ = {isa = PBXBuildFile; fileRef = A6498DF71D1A40CC00ADD9E1 /* ContainerModel.json */; }; 23 | A6498E0D1D1A40CC00ADD9E1 /* DifferentJSONKey.json in Resources */ = {isa = PBXBuildFile; fileRef = A6498DFB1D1A40CC00ADD9E1 /* DifferentJSONKey.json */; }; 24 | A6498E0E1D1A40CC00ADD9E1 /* DifferentJSONKey.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498DFC1D1A40CC00ADD9E1 /* DifferentJSONKey.m */; }; 25 | A6498E0F1D1A40CC00ADD9E1 /* DoubleModel.json in Resources */ = {isa = PBXBuildFile; fileRef = A6498DFD1D1A40CC00ADD9E1 /* DoubleModel.json */; }; 26 | A6498E121D1A40CC00ADD9E1 /* SimpleModel.json in Resources */ = {isa = PBXBuildFile; fileRef = A6498E021D1A40CC00ADD9E1 /* SimpleModel.json */; }; 27 | A6498E131D1A40CC00ADD9E1 /* User.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498E041D1A40CC00ADD9E1 /* User.m */; }; 28 | A6498E1B1D1A41C400ADD9E1 /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498E171D1A41C400ADD9E1 /* NSObject+YYModel.m */; }; 29 | A6498E1C1D1A41C400ADD9E1 /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498E191D1A41C400ADD9E1 /* YYClassInfo.m */; }; 30 | A6498E261D1A437800ADD9E1 /* ContainerModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A6498E251D1A437800ADD9E1 /* ContainerModel.m */; }; 31 | A6498E2A1D1A871500ADD9E1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A6498E291D1A871500ADD9E1 /* Main.storyboard */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | A60645BF1D20CFB400EFA8E6 /* timestamp.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = timestamp.json; sourceTree = ""; }; 36 | A60645C11D20D01400EFA8E6 /* TimeStampModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeStampModel.h; sourceTree = ""; }; 37 | A60645C21D20D01400EFA8E6 /* TimeStampModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeStampModel.m; sourceTree = ""; }; 38 | A60645C41D20DAE500EFA8E6 /* DemoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoModel.h; sourceTree = ""; }; 39 | A60645C51D20DAE500EFA8E6 /* DemoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoModel.m; sourceTree = ""; }; 40 | A6498DD31D1A403C00ADD9E1 /* YYModel-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "YYModel-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | A6498DD71D1A403C00ADD9E1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 42 | A6498DD91D1A403C00ADD9E1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | A6498DDA1D1A403C00ADD9E1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | A6498DE21D1A403C00ADD9E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | A6498DE51D1A403C00ADD9E1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | A6498DE71D1A403C00ADD9E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | A6498DED1D1A407800ADD9E1 /* BGTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGTableViewController.h; sourceTree = ""; }; 48 | A6498DEE1D1A407800ADD9E1 /* BGTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGTableViewController.m; sourceTree = ""; }; 49 | A6498DF01D1A40CC00ADD9E1 /* Author.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Author.h; sourceTree = ""; }; 50 | A6498DF11D1A40CC00ADD9E1 /* Author.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Author.m; sourceTree = ""; }; 51 | A6498DF21D1A40CC00ADD9E1 /* BlacklistAndWhitelist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlacklistAndWhitelist.h; sourceTree = ""; }; 52 | A6498DF31D1A40CC00ADD9E1 /* BlacklistAndWhitelist.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = BlacklistAndWhitelist.json; sourceTree = ""; }; 53 | A6498DF41D1A40CC00ADD9E1 /* BlacklistAndWhitelist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlacklistAndWhitelist.m; sourceTree = ""; }; 54 | A6498DF51D1A40CC00ADD9E1 /* Book.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Book.h; sourceTree = ""; }; 55 | A6498DF61D1A40CC00ADD9E1 /* Book.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Book.m; sourceTree = ""; }; 56 | A6498DF71D1A40CC00ADD9E1 /* ContainerModel.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ContainerModel.json; sourceTree = ""; }; 57 | A6498DFA1D1A40CC00ADD9E1 /* DifferentJSONKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DifferentJSONKey.h; sourceTree = ""; }; 58 | A6498DFB1D1A40CC00ADD9E1 /* DifferentJSONKey.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DifferentJSONKey.json; sourceTree = ""; }; 59 | A6498DFC1D1A40CC00ADD9E1 /* DifferentJSONKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DifferentJSONKey.m; sourceTree = ""; }; 60 | A6498DFD1D1A40CC00ADD9E1 /* DoubleModel.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DoubleModel.json; sourceTree = ""; }; 61 | A6498E021D1A40CC00ADD9E1 /* SimpleModel.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SimpleModel.json; sourceTree = ""; }; 62 | A6498E031D1A40CC00ADD9E1 /* User.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = User.h; sourceTree = ""; }; 63 | A6498E041D1A40CC00ADD9E1 /* User.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = User.m; sourceTree = ""; }; 64 | A6498E161D1A41C400ADD9E1 /* NSObject+YYModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+YYModel.h"; sourceTree = ""; }; 65 | A6498E171D1A41C400ADD9E1 /* NSObject+YYModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+YYModel.m"; sourceTree = ""; }; 66 | A6498E181D1A41C400ADD9E1 /* YYClassInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYClassInfo.h; sourceTree = ""; }; 67 | A6498E191D1A41C400ADD9E1 /* YYClassInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYClassInfo.m; sourceTree = ""; }; 68 | A6498E1A1D1A41C400ADD9E1 /* YYModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYModel.h; sourceTree = ""; }; 69 | A6498E241D1A437800ADD9E1 /* ContainerModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContainerModel.h; path = ../ContainerModel.h; sourceTree = ""; }; 70 | A6498E251D1A437800ADD9E1 /* ContainerModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContainerModel.m; path = ../ContainerModel.m; sourceTree = ""; }; 71 | A6498E291D1A871500ADD9E1 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | A6498DD01D1A403B00ADD9E1 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | /* End PBXFrameworksBuildPhase section */ 83 | 84 | /* Begin PBXGroup section */ 85 | A6498DCA1D1A403B00ADD9E1 = { 86 | isa = PBXGroup; 87 | children = ( 88 | A6498DD51D1A403C00ADD9E1 /* YYModel-Demo */, 89 | A6498DD41D1A403C00ADD9E1 /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | A6498DD41D1A403C00ADD9E1 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | A6498DD31D1A403C00ADD9E1 /* YYModel-Demo.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | A6498DD51D1A403C00ADD9E1 /* YYModel-Demo */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | A6498E151D1A41C400ADD9E1 /* YYModel */, 105 | A6498DD91D1A403C00ADD9E1 /* AppDelegate.h */, 106 | A6498DDA1D1A403C00ADD9E1 /* AppDelegate.m */, 107 | A6498DED1D1A407800ADD9E1 /* BGTableViewController.h */, 108 | A6498DEE1D1A407800ADD9E1 /* BGTableViewController.m */, 109 | A6498E1D1D1A423400ADD9E1 /* Json */, 110 | A6498E221D1A42CB00ADD9E1 /* Model */, 111 | A6498E231D1A435800ADD9E1 /* ContainModel */, 112 | A6498E201D1A428300ADD9E1 /* DifferenyJsonKey */, 113 | A6498E211D1A429300ADD9E1 /* WhiteListAndBlacklist */, 114 | A6498E1F1D1A425800ADD9E1 /* DoubleModel */, 115 | A6498E1E1D1A425600ADD9E1 /* SimpleModel */, 116 | A6498DE21D1A403C00ADD9E1 /* Assets.xcassets */, 117 | A6498E291D1A871500ADD9E1 /* Main.storyboard */, 118 | A6498DE41D1A403C00ADD9E1 /* LaunchScreen.storyboard */, 119 | A6498DE71D1A403C00ADD9E1 /* Info.plist */, 120 | A6498DD61D1A403C00ADD9E1 /* Supporting Files */, 121 | ); 122 | path = "YYModel-Demo"; 123 | sourceTree = ""; 124 | }; 125 | A6498DD61D1A403C00ADD9E1 /* Supporting Files */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | A6498DD71D1A403C00ADD9E1 /* main.m */, 129 | ); 130 | name = "Supporting Files"; 131 | sourceTree = ""; 132 | }; 133 | A6498E151D1A41C400ADD9E1 /* YYModel */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | A6498E161D1A41C400ADD9E1 /* NSObject+YYModel.h */, 137 | A6498E171D1A41C400ADD9E1 /* NSObject+YYModel.m */, 138 | A6498E181D1A41C400ADD9E1 /* YYClassInfo.h */, 139 | A6498E191D1A41C400ADD9E1 /* YYClassInfo.m */, 140 | A6498E1A1D1A41C400ADD9E1 /* YYModel.h */, 141 | ); 142 | path = YYModel; 143 | sourceTree = ""; 144 | }; 145 | A6498E1D1D1A423400ADD9E1 /* Json */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | A6498DF31D1A40CC00ADD9E1 /* BlacklistAndWhitelist.json */, 149 | A6498DF71D1A40CC00ADD9E1 /* ContainerModel.json */, 150 | A6498DFB1D1A40CC00ADD9E1 /* DifferentJSONKey.json */, 151 | A6498DFD1D1A40CC00ADD9E1 /* DoubleModel.json */, 152 | A6498E021D1A40CC00ADD9E1 /* SimpleModel.json */, 153 | A60645BF1D20CFB400EFA8E6 /* timestamp.json */, 154 | ); 155 | name = Json; 156 | sourceTree = ""; 157 | }; 158 | A6498E1E1D1A425600ADD9E1 /* SimpleModel */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | A6498E031D1A40CC00ADD9E1 /* User.h */, 162 | A6498E041D1A40CC00ADD9E1 /* User.m */, 163 | ); 164 | name = SimpleModel; 165 | sourceTree = ""; 166 | }; 167 | A6498E1F1D1A425800ADD9E1 /* DoubleModel */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | A6498DF01D1A40CC00ADD9E1 /* Author.h */, 171 | A6498DF11D1A40CC00ADD9E1 /* Author.m */, 172 | A6498DF51D1A40CC00ADD9E1 /* Book.h */, 173 | A6498DF61D1A40CC00ADD9E1 /* Book.m */, 174 | ); 175 | name = DoubleModel; 176 | sourceTree = ""; 177 | }; 178 | A6498E201D1A428300ADD9E1 /* DifferenyJsonKey */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | A6498DFA1D1A40CC00ADD9E1 /* DifferentJSONKey.h */, 182 | A6498DFC1D1A40CC00ADD9E1 /* DifferentJSONKey.m */, 183 | ); 184 | name = DifferenyJsonKey; 185 | sourceTree = ""; 186 | }; 187 | A6498E211D1A429300ADD9E1 /* WhiteListAndBlacklist */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | A6498DF21D1A40CC00ADD9E1 /* BlacklistAndWhitelist.h */, 191 | A6498DF41D1A40CC00ADD9E1 /* BlacklistAndWhitelist.m */, 192 | ); 193 | name = WhiteListAndBlacklist; 194 | sourceTree = ""; 195 | }; 196 | A6498E221D1A42CB00ADD9E1 /* Model */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | A6498DFE1D1A40CC00ADD9E1 /* latestExpireBonusModel.h */, 200 | A6498DFF1D1A40CC00ADD9E1 /* latestExpireBonusModel.m */, 201 | A6498E051D1A40CC00ADD9E1 /* YGXModel.h */, 202 | A6498E061D1A40CC00ADD9E1 /* YGXModel.m */, 203 | A6498E001D1A40CC00ADD9E1 /* ListModel.h */, 204 | A6498E011D1A40CC00ADD9E1 /* ListModel.m */, 205 | A6498DF81D1A40CC00ADD9E1 /* DataModel.h */, 206 | A6498DF91D1A40CC00ADD9E1 /* DataModel.m */, 207 | A60645C11D20D01400EFA8E6 /* TimeStampModel.h */, 208 | A60645C21D20D01400EFA8E6 /* TimeStampModel.m */, 209 | ); 210 | name = Model; 211 | sourceTree = ""; 212 | }; 213 | A6498E231D1A435800ADD9E1 /* ContainModel */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | A6498E241D1A437800ADD9E1 /* ContainerModel.h */, 217 | A6498E251D1A437800ADD9E1 /* ContainerModel.m */, 218 | A60645C41D20DAE500EFA8E6 /* DemoModel.h */, 219 | A60645C51D20DAE500EFA8E6 /* DemoModel.m */, 220 | ); 221 | name = ContainModel; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXGroup section */ 225 | 226 | /* Begin PBXNativeTarget section */ 227 | A6498DD21D1A403B00ADD9E1 /* YYModel-Demo */ = { 228 | isa = PBXNativeTarget; 229 | buildConfigurationList = A6498DEA1D1A403C00ADD9E1 /* Build configuration list for PBXNativeTarget "YYModel-Demo" */; 230 | buildPhases = ( 231 | A6498DCF1D1A403B00ADD9E1 /* Sources */, 232 | A6498DD01D1A403B00ADD9E1 /* Frameworks */, 233 | A6498DD11D1A403B00ADD9E1 /* Resources */, 234 | ); 235 | buildRules = ( 236 | ); 237 | dependencies = ( 238 | ); 239 | name = "YYModel-Demo"; 240 | productName = "YYModel-Demo"; 241 | productReference = A6498DD31D1A403C00ADD9E1 /* YYModel-Demo.app */; 242 | productType = "com.apple.product-type.application"; 243 | }; 244 | /* End PBXNativeTarget section */ 245 | 246 | /* Begin PBXProject section */ 247 | A6498DCB1D1A403B00ADD9E1 /* Project object */ = { 248 | isa = PBXProject; 249 | attributes = { 250 | LastUpgradeCheck = 0730; 251 | ORGANIZATIONNAME = walker; 252 | TargetAttributes = { 253 | A6498DD21D1A403B00ADD9E1 = { 254 | CreatedOnToolsVersion = 7.3.1; 255 | DevelopmentTeam = A9DWS95K67; 256 | }; 257 | }; 258 | }; 259 | buildConfigurationList = A6498DCE1D1A403B00ADD9E1 /* Build configuration list for PBXProject "YYModel-Demo" */; 260 | compatibilityVersion = "Xcode 3.2"; 261 | developmentRegion = English; 262 | hasScannedForEncodings = 0; 263 | knownRegions = ( 264 | en, 265 | Base, 266 | ); 267 | mainGroup = A6498DCA1D1A403B00ADD9E1; 268 | productRefGroup = A6498DD41D1A403C00ADD9E1 /* Products */; 269 | projectDirPath = ""; 270 | projectRoot = ""; 271 | targets = ( 272 | A6498DD21D1A403B00ADD9E1 /* YYModel-Demo */, 273 | ); 274 | }; 275 | /* End PBXProject section */ 276 | 277 | /* Begin PBXResourcesBuildPhase section */ 278 | A6498DD11D1A403B00ADD9E1 /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | A6498DE61D1A403C00ADD9E1 /* LaunchScreen.storyboard in Resources */, 283 | A6498DE31D1A403C00ADD9E1 /* Assets.xcassets in Resources */, 284 | A6498E121D1A40CC00ADD9E1 /* SimpleModel.json in Resources */, 285 | A6498E2A1D1A871500ADD9E1 /* Main.storyboard in Resources */, 286 | A6498E0B1D1A40CC00ADD9E1 /* ContainerModel.json in Resources */, 287 | A6498E081D1A40CC00ADD9E1 /* BlacklistAndWhitelist.json in Resources */, 288 | A6498E0F1D1A40CC00ADD9E1 /* DoubleModel.json in Resources */, 289 | A60645C01D20CFB400EFA8E6 /* timestamp.json in Resources */, 290 | A6498E0D1D1A40CC00ADD9E1 /* DifferentJSONKey.json in Resources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXResourcesBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | A6498DCF1D1A403B00ADD9E1 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | A60645C31D20D01400EFA8E6 /* TimeStampModel.m in Sources */, 302 | A6498E1B1D1A41C400ADD9E1 /* NSObject+YYModel.m in Sources */, 303 | A6498DDB1D1A403C00ADD9E1 /* AppDelegate.m in Sources */, 304 | A6498DD81D1A403C00ADD9E1 /* main.m in Sources */, 305 | A6498E071D1A40CC00ADD9E1 /* Author.m in Sources */, 306 | A6498DEF1D1A407800ADD9E1 /* BGTableViewController.m in Sources */, 307 | A6498E1C1D1A41C400ADD9E1 /* YYClassInfo.m in Sources */, 308 | A6498E131D1A40CC00ADD9E1 /* User.m in Sources */, 309 | A6498E0C1D1A40CC00ADD9E1 /* DataModel.m in Sources */, 310 | A60645C61D20DAE500EFA8E6 /* DemoModel.m in Sources */, 311 | A6498E0A1D1A40CC00ADD9E1 /* Book.m in Sources */, 312 | A6498E091D1A40CC00ADD9E1 /* BlacklistAndWhitelist.m in Sources */, 313 | A6498E261D1A437800ADD9E1 /* ContainerModel.m in Sources */, 314 | A6498E0E1D1A40CC00ADD9E1 /* DifferentJSONKey.m in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXSourcesBuildPhase section */ 319 | 320 | /* Begin PBXVariantGroup section */ 321 | A6498DE41D1A403C00ADD9E1 /* LaunchScreen.storyboard */ = { 322 | isa = PBXVariantGroup; 323 | children = ( 324 | A6498DE51D1A403C00ADD9E1 /* Base */, 325 | ); 326 | name = LaunchScreen.storyboard; 327 | sourceTree = ""; 328 | }; 329 | /* End PBXVariantGroup section */ 330 | 331 | /* Begin XCBuildConfiguration section */ 332 | A6498DE81D1A403C00ADD9E1 /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = dwarf; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | ENABLE_TESTABILITY = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 370 | MTL_ENABLE_DEBUG_INFO = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | SDKROOT = iphoneos; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | }; 375 | name = Debug; 376 | }; 377 | A6498DE91D1A403C00ADD9E1 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = iphoneos; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | VALIDATE_PRODUCT = YES; 413 | }; 414 | name = Release; 415 | }; 416 | A6498DEB1D1A403C00ADD9E1 /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 420 | INFOPLIST_FILE = "YYModel-Demo/Info.plist"; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = "cn.sharma.www-bin.YYModel-Demo"; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | }; 425 | name = Debug; 426 | }; 427 | A6498DEC1D1A403C00ADD9E1 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 431 | INFOPLIST_FILE = "YYModel-Demo/Info.plist"; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = "cn.sharma.www-bin.YYModel-Demo"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | }; 436 | name = Release; 437 | }; 438 | /* End XCBuildConfiguration section */ 439 | 440 | /* Begin XCConfigurationList section */ 441 | A6498DCE1D1A403B00ADD9E1 /* Build configuration list for PBXProject "YYModel-Demo" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | A6498DE81D1A403C00ADD9E1 /* Debug */, 445 | A6498DE91D1A403C00ADD9E1 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | A6498DEA1D1A403C00ADD9E1 /* Build configuration list for PBXNativeTarget "YYModel-Demo" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | A6498DEB1D1A403C00ADD9E1 /* Debug */, 454 | A6498DEC1D1A403C00ADD9E1 /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | /* End XCConfigurationList section */ 460 | }; 461 | rootObject = A6498DCB1D1A403B00ADD9E1 /* Project object */; 462 | } 463 | -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/project.xcworkspace/xcuserdata/guobin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkertop/YYModel---Demo/c385cccd820180426eed86644c2094d320f9bcaf/YYModel-Demo.xcodeproj/project.xcworkspace/xcuserdata/guobin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/xcuserdata/guobin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | <<<<<<< HEAD 6 | 7 | 9 | 19 | 20 | 21 | 23 | 35 | 36 | 37 | 39 | 51 | 52 | 53 | 54 | ======= 55 | >>>>>>> origin/master 56 | 57 | -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/xcuserdata/guobin.xcuserdatad/xcschemes/YYModel-Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /YYModel-Demo.xcodeproj/xcuserdata/guobin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YYModel-Demo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | A6498DD21D1A403B00ADD9E1 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /YYModel-Demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/22. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /YYModel-Demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/22. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /YYModel-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /YYModel-Demo/Author.h: -------------------------------------------------------------------------------- 1 | // 2 | // Author.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Author : NSObject 12 | 13 | @property(nonatomic,copy)NSString *name; 14 | @property(nonatomic,copy)NSData *birthday; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /YYModel-Demo/Author.m: -------------------------------------------------------------------------------- 1 | // 2 | // Author.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "Author.h" 10 | 11 | @implementation Author 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YYModel-Demo/BGTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BGTableViewController.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BGTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YYModel-Demo/BGTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BGTableViewController.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "BGTableViewController.h" 10 | #import "Book.h" 11 | #import "User.h" 12 | #import "DifferentJSONKey.h" 13 | #import "BlacklistAndWhitelist.h" 14 | #import "ContainerModel.h" 15 | #import "TimeStampModel.h" 16 | 17 | @interface BGTableViewController () 18 | 19 | @property(nonatomic,strong)NSArray *demoArray; 20 | 21 | @property(nonatomic,strong) NSArray *listArray; //listArray 22 | 23 | @end 24 | 25 | @implementation BGTableViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | self.demoArray = @[@"SimpleModel(简答的数据模型)",@"DoubleModel(双模型)",@"DifferentJSONKey(键值和属性不同)",@"Container property(容器模型)",@"whiteList&blackList(黑白名单)",@"timeStamp"]; 30 | 31 | self.demoArray = @[@"SimpleModel(简答的数据模型)",@"DoubleModel(双模型)",@"DifferentJSONKey(键值和属性不同)",@"Container property(容器模型)",@"whiteList&blackList(黑白名单)"]; 32 | 33 | self.tableView.backgroundColor = [UIColor redColor]; 34 | self.tableView.tableFooterView = [[UITableViewHeaderFooterView alloc]init]; 35 | self.tableView.rowHeight = 100; 36 | self.tableView.estimatedRowHeight = 10; 37 | } 38 | 39 | #pragma mark - Table view data source 40 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 41 | return 1; 42 | } 43 | 44 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 45 | return self.demoArray.count; 46 | } 47 | 48 | // cell的数据源方法 49 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 50 | // 实例化cell 51 | UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil]; 52 | 53 | cell.textLabel.text = self.demoArray[indexPath.row]; 54 | 55 | return cell; 56 | } 57 | 58 | #pragma mark - delegate 59 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 60 | { 61 | switch (indexPath.row) { 62 | case 0: 63 | [self simpleModelJsonModelConvert]; 64 | break; 65 | case 1: 66 | [self DoubleModelJsonModelConvert]; 67 | break; 68 | case 2: 69 | [self DifferentJSONKeyModelConvert]; 70 | break; 71 | case 3: 72 | [self containerJsonModelConvert]; 73 | break; 74 | case 4: 75 | // [self BlacklistAndWhitelistModelConvert]; 76 | case 5: 77 | [self timestamp]; 78 | [self BlacklistAndWhitelistModelConvert]; 79 | default: 80 | break; 81 | } 82 | } 83 | 84 | #pragma mark - custom Method 85 | //读取本地json,获取json数据 86 | - (NSDictionary *) getJsonWithJsonName:(NSString *)jsonName { 87 | //从本地读取json数据(这一步你从网络里面请求) 88 | NSString *path = [[NSBundle mainBundle]pathForResource:jsonName ofType:@"json"]; 89 | NSData *data = [NSData dataWithContentsOfFile:path]; 90 | return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 91 | } 92 | 93 | - (NSArray *) getJsonArrayWithJsonName:(NSString *)jsonName { 94 | //从本地读取json数据(这一步你从网络里面请求) 95 | NSString *path = [[NSBundle mainBundle]pathForResource:jsonName ofType:@"json"]; 96 | NSData *data = [NSData dataWithContentsOfFile:path]; 97 | return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 98 | } 99 | 100 | - (void) simpleModelJsonModelConvert { 101 | NSDictionary *json = [self getJsonWithJsonName:@"SimpleModel"]; 102 | // Convert json to model: 103 | User *user = [User yy_modelWithDictionary:json]; 104 | NSLog(@"%@",user); 105 | 106 | // Convert model to json: 107 | NSDictionary *jsonConvert = [user yy_modelToJSONObject]; 108 | NSLog(@"%@",jsonConvert); 109 | } 110 | 111 | - (void) DoubleModelJsonModelConvert { 112 | NSDictionary *json = [self getJsonWithJsonName:@"DoubleModel"]; 113 | 114 | // Convert json to model: 115 | Book *book = [Book yy_modelWithDictionary:json]; 116 | NSLog(@"book ===== %@",book); 117 | 118 | // Convert model to json: 119 | NSDictionary *jsonDict = [book yy_modelToJSONObject]; 120 | NSLog(@"jsonDict ===== %@",jsonDict); 121 | } 122 | 123 | - (void) DifferentJSONKeyModelConvert { 124 | NSDictionary *json = [self getJsonWithJsonName:@"DifferentJSONKey"]; 125 | 126 | // Convert json to model: 127 | DifferentJSONKey *differentJsonKey = [DifferentJSONKey yy_modelWithDictionary:json]; 128 | 129 | // Convert model to json: 130 | NSDictionary *jsonDict = [differentJsonKey yy_modelToJSONObject]; 131 | NSLog(@"jsonDict ===== %@",jsonDict); 132 | } 133 | 134 | - (void) containerJsonModelConvert { 135 | NSDictionary *json =[self getJsonWithJsonName:@"ContainerModel"]; 136 | 137 | ContainerModel *containModel = [ContainerModel yy_modelWithDictionary:json]; 138 | 139 | NSDictionary *dataDict = [containModel valueForKey:@"data"]; 140 | 141 | self.listArray = [dataDict valueForKey:@"list"]; 142 | 143 | //遍历数组获取里面的字典,在调用YYModel方法 144 | [self.listArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 145 | NSDictionary *listDict = obj; 146 | List *listModel = [List yy_modelWithDictionary:listDict]; 147 | //随便获取count 和 id两个类型 148 | NSString *count = [listModel valueForKey:@"count"]; 149 | NSString *id = [listModel valueForKey:@"id"]; 150 | NSLog(@"count == %@,id === %@",count,id); 151 | }]; 152 | } 153 | 154 | 155 | - (void) BlacklistAndWhitelistModelConvert { 156 | NSDictionary *json = [self getJsonWithJsonName:@"BlacklistAndWhitelist"]; 157 | 158 | // Convert json to model: 159 | BlacklistAndWhitelist *blacklistAndWhitelist = [BlacklistAndWhitelist yy_modelWithDictionary:json]; 160 | 161 | // Convert model to json: 162 | NSDictionary *jsonDict = [blacklistAndWhitelist yy_modelToJSONObject]; 163 | NSLog(@"jsonDict ===== %@",jsonDict); 164 | } 165 | 166 | - (void) timestamp { 167 | NSDictionary *timeDict = [self getJsonWithJsonName:@"timestamp"]; 168 | TimeStampModel *timestamp = [TimeStampModel yy_modelWithDictionary:timeDict]; 169 | NSLog(@"%@",timestamp.createdAt); 170 | } 171 | 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /YYModel-Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YYModel-Demo/BlacklistAndWhitelist.h: -------------------------------------------------------------------------------- 1 | // 2 | // Blacklist&Whitelist.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YYModel.h" 11 | 12 | @interface BlacklistAndWhitelist : NSObject 13 | 14 | @property (nonatomic, assign) NSInteger uid; 15 | @property (nonatomic,copy) NSString *name; 16 | @property (nonatomic,copy) NSDate *created; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YYModel-Demo/BlacklistAndWhitelist.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid":123456, 3 | "name":"Harry", 4 | "created":"1965-07-31T00:00:00+0000" 5 | } 6 | -------------------------------------------------------------------------------- /YYModel-Demo/BlacklistAndWhitelist.m: -------------------------------------------------------------------------------- 1 | // 2 | // Blacklist&Whitelist.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "BlacklistAndWhitelist.h" 10 | 11 | @implementation BlacklistAndWhitelist 12 | 13 | + (NSArray *)modelPropertyWhitelist { 14 | return @[@"name"]; 15 | } 16 | //+ (NSArray *)modelPropertyBlacklist { 17 | // return @[@"uid"]; 18 | //} 19 | @end 20 | -------------------------------------------------------------------------------- /YYModel-Demo/Book.h: -------------------------------------------------------------------------------- 1 | // 2 | // Book.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Author.h" 11 | #import "YYModel.h" 12 | 13 | 14 | 15 | @interface Book : NSObject 16 | 17 | @property(nonatomic,copy)NSString *name; 18 | @property(nonatomic,assign)NSUInteger pages; 19 | @property Author *author; //Book 包含 Author 属性 20 | //更改之后的name 21 | //@property(nonatomic,strong)NSString *namePlus; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /YYModel-Demo/Book.m: -------------------------------------------------------------------------------- 1 | // 2 | // Book.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "Book.h" 10 | 11 | @implementation Book 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YYModel-Demo/ContainerModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "count": "63", 4 | "latest_expire_bonus": { 5 | "minutes": "353天6小时52分钟", 6 | "money": "20.00" 7 | }, 8 | "list": [ 9 | { 10 | "count": "2", 11 | "createdAt": "2015-07-31 15:55:07", 12 | "expiredAt": "2015-08-01 15:55:06", 13 | "flag": 2, 14 | "id": "14381", 15 | "leftNum": 2, 16 | "sendNum": "0", 17 | "usedNum": "0" 18 | }, 19 | { 20 | "count": "4", 21 | "createdAt": "2015-07-23 11:54:02", 22 | "expiredAt": "2015-07-24 11:54:01", 23 | "flag": 2, 24 | "id": "13791", 25 | "leftNum": 4, 26 | "sendNum": "0", 27 | "usedNum": "0" 28 | }, 29 | { 30 | "count": "4", 31 | "createdAt": "2015-07-23 11:54:02", 32 | "expiredAt": "2015-07-24 11:54:01", 33 | "flag": 2, 34 | "id": "13790", 35 | "leftNum": 4, 36 | "sendNum": "0", 37 | "usedNum": "0" 38 | }, 39 | { 40 | "count": "2", 41 | "createdAt": "2015-07-08 19:07:02", 42 | "expiredAt": "2015-07-09 19:07:01", 43 | "flag": 2, 44 | "id": "13459", 45 | "leftNum": 2, 46 | "sendNum": "0", 47 | "usedNum": "0" 48 | }, 49 | { 50 | "count": "7", 51 | "createdAt": "2015-07-06 13:26:14", 52 | "expiredAt": "2015-07-07 13:26:13", 53 | "flag": 2, 54 | "id": "13189", 55 | "leftNum": 7, 56 | "sendNum": "0", 57 | "usedNum": "0" 58 | }, 59 | { 60 | "count": "7", 61 | "createdAt": "2015-07-06 13:26:05", 62 | "expiredAt": "2015-07-07 13:26:04", 63 | "flag": 2, 64 | "id": "12928", 65 | "leftNum": 7, 66 | "sendNum": "0", 67 | "usedNum": "0" 68 | }, 69 | { 70 | "count": "6", 71 | "createdAt": "2015-07-06 13:26:02", 72 | "expiredAt": "2015-07-07 13:26:01", 73 | "flag": 2, 74 | "id": "12667", 75 | "leftNum": 6, 76 | "sendNum": "0", 77 | "usedNum": "0" 78 | }, 79 | { 80 | "count": "5", 81 | "createdAt": "2015-07-06 13:25:22", 82 | "expiredAt": "2015-07-07 13:25:21", 83 | "flag": 2, 84 | "id": "12406", 85 | "leftNum": 5, 86 | "sendNum": "0", 87 | "usedNum": "0" 88 | }, 89 | { 90 | "count": "5", 91 | "createdAt": "2015-07-06 13:25:04", 92 | "expiredAt": "2015-07-07 13:25:03", 93 | "flag": 2, 94 | "id": "12145", 95 | "leftNum": 5, 96 | "sendNum": "0", 97 | "usedNum": "0" 98 | }, 99 | { 100 | "count": "4", 101 | "createdAt": "2015-07-06 13:25:01", 102 | "expiredAt": "2015-07-07 13:25:00", 103 | "flag": 2, 104 | "id": "11884", 105 | "leftNum": 4, 106 | "sendNum": "0", 107 | "usedNum": "0" 108 | } 109 | ] 110 | }, 111 | "errno": 3, 112 | "error": "2" 113 | } -------------------------------------------------------------------------------- /YYModel-Demo/DemoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoModel.h 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/27. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //1, 创建一个集成自NSobject的DemoModel 12 | //2, 使用快捷键进行操作 13 | //3, 修改/不修改创建数据模型的名称 14 | @class Author; 15 | @interface DemoModel : NSObject 16 | 17 | @property (nonatomic, copy) NSString *name; 18 | 19 | @property (nonatomic, strong) Author *author; 20 | 21 | @property (nonatomic, assign) NSInteger pages; 22 | 23 | @end 24 | @interface Author : NSObject 25 | 26 | @property (nonatomic, copy) NSString *name; 27 | 28 | @property (nonatomic, copy) NSString *birthday; 29 | 30 | @end 31 | 32 | -------------------------------------------------------------------------------- /YYModel-Demo/DemoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoModel.m 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/27. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "DemoModel.h" 10 | 11 | @implementation DemoModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YYModel-Demo/DifferentJSONKey.h: -------------------------------------------------------------------------------- 1 | // 2 | // DifferentJSONKey.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YYModel.h" 11 | 12 | 13 | @interface DifferentJSONKey : NSObject 14 | 15 | @property(nonatomic,copy) NSString *UserID; 16 | @property(nonatomic,copy) NSString *createdTime; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YYModel-Demo/DifferentJSONKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_id":123456, 3 | "created_at":"2016-05-31T00:00:00+0000" 4 | } 5 | -------------------------------------------------------------------------------- /YYModel-Demo/DifferentJSONKey.m: -------------------------------------------------------------------------------- 1 | // 2 | // DifferentJSONKey.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "DifferentJSONKey.h" 10 | 11 | @implementation DifferentJSONKey 12 | 13 | + (NSDictionary *) modelCustomPropertyMapper { 14 | return @{@"UserID" : @"user_id", 15 | @"createdTime" : @"created_at" 16 | }; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /YYModel-Demo/DoubleModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "author":{ 3 | "name":"J.K.Rowling", 4 | "birthday":"1965-07-31T00:00:00+0000" 5 | }, 6 | "name":"Harry Potter", 7 | "pages":256 8 | } 9 | -------------------------------------------------------------------------------- /YYModel-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /YYModel-Demo/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /YYModel-Demo/SimpleModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid":123456, 3 | "name":"Harry", 4 | "created":"1965-07-31T00:00:00+0000" 5 | } -------------------------------------------------------------------------------- /YYModel-Demo/TimeStampModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeStampModel.h 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/27. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TimeStampModel : NSObject 12 | 13 | @property (nonatomic, copy) NSString *name; 14 | 15 | @property (nonatomic, assign) NSDate *createdAt; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /YYModel-Demo/TimeStampModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeStampModel.m 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/27. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "TimeStampModel.h" 10 | 11 | @implementation TimeStampModel 12 | 13 | - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { 14 | NSNumber *timestamp = dic[@"timestamp"]; 15 | if (![timestamp isKindOfClass:[NSNumber class]]) return NO; 16 | _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; 17 | return YES; 18 | } 19 | 20 | //- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { 21 | // if (!_createdAt) return NO; 22 | // dic[@"timestamp"] = @(n.timeIntervalSince1970); 23 | // return YES; 24 | //} 25 | @end 26 | -------------------------------------------------------------------------------- /YYModel-Demo/User.h: -------------------------------------------------------------------------------- 1 | // 2 | // User.h 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YYModel.h" 11 | 12 | @interface User : NSObject 13 | 14 | @property (nonatomic, assign) NSInteger uid; 15 | @property (nonatomic,copy) NSString *name; 16 | @property (nonatomic,copy) NSDate *created; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YYModel-Demo/User.m: -------------------------------------------------------------------------------- 1 | // 2 | // User.m 3 | // Demo-YYModel 4 | // 5 | // Created by 郭彬 on 16/6/20. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import "User.h" 10 | 11 | @implementation User 12 | 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /YYModel-Demo/YYModel/NSObject+YYModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+YYModel.h 3 | // YYModel 4 | // 5 | // Created by ibireme on 15/5/10. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Provide some data-model method: 18 | 19 | * Convert json to any object, or convert any object to json. 20 | * Set object properties with a key-value dictionary (like KVC). 21 | * Implementations of `NSCoding`, `NSCopying`, `-hash` and `-isEqual:`. 22 | 23 | See `YYModel` protocol for custom methods. 24 | 25 | 26 | Sample Code: 27 | 28 | ********************** json convertor ********************* 29 | @interface YYAuthor : NSObject 30 | @property (nonatomic, strong) NSString *name; 31 | @property (nonatomic, assign) NSDate *birthday; 32 | @end 33 | @implementation YYAuthor 34 | @end 35 | 36 | @interface YYBook : NSObject 37 | @property (nonatomic, copy) NSString *name; 38 | @property (nonatomic, assign) NSUInteger pages; 39 | @property (nonatomic, strong) YYAuthor *author; 40 | @end 41 | @implementation YYBook 42 | @end 43 | 44 | int main() { 45 | // create model from json 46 | YYBook *book = [YYBook yy_modelWithJSON:@"{\"name\": \"Harry Potter\", \"pages\": 256, \"author\": {\"name\": \"J.K.Rowling\", \"birthday\": \"1965-07-31\" }}"]; 47 | 48 | // convert model to json 49 | NSString *json = [book yy_modelToJSONString]; 50 | // {"author":{"name":"J.K.Rowling","birthday":"1965-07-31T00:00:00+0000"},"name":"Harry Potter","pages":256} 51 | } 52 | 53 | ********************** Coding/Copying/hash/equal ********************* 54 | @interface YYShadow :NSObject 55 | @property (nonatomic, copy) NSString *name; 56 | @property (nonatomic, assign) CGSize size; 57 | @end 58 | 59 | @implementation YYShadow 60 | - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } 61 | - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; } 62 | - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } 63 | - (NSUInteger)hash { return [self yy_modelHash]; } 64 | - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; } 65 | @end 66 | 67 | */ 68 | @interface NSObject (YYModel) 69 | 70 | /** 71 | Creates and returns a new instance of the receiver from a json. 72 | This method is thread-safe. 73 | 74 | @param json A json object in `NSDictionary`, `NSString` or `NSData`. 75 | 76 | @return A new instance created from the json, or nil if an error occurs. 77 | */ 78 | + (nullable instancetype)yy_modelWithJSON:(id)json; 79 | 80 | /** 81 | Creates and returns a new instance of the receiver from a key-value dictionary. 82 | This method is thread-safe. 83 | 84 | @param dictionary A key-value dictionary mapped to the instance's properties. 85 | Any invalid key-value pair in dictionary will be ignored. 86 | 87 | @return A new instance created from the dictionary, or nil if an error occurs. 88 | 89 | @discussion The key in `dictionary` will mapped to the reciever's property name, 90 | and the value will set to the property. If the value's type does not match the 91 | property, this method will try to convert the value based on these rules: 92 | 93 | `NSString` or `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger... 94 | `NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd". 95 | `NSString` -> NSURL. 96 | `NSValue` -> struct or union, such as CGRect, CGSize, ... 97 | `NSString` -> SEL, Class. 98 | */ 99 | + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary; 100 | 101 | /** 102 | Set the receiver's properties with a json object. 103 | 104 | @discussion Any invalid data in json will be ignored. 105 | 106 | @param json A json object of `NSDictionary`, `NSString` or `NSData`, mapped to the 107 | receiver's properties. 108 | 109 | @return Whether succeed. 110 | */ 111 | - (BOOL)yy_modelSetWithJSON:(id)json; 112 | 113 | /** 114 | Set the receiver's properties with a key-value dictionary. 115 | 116 | @param dic A key-value dictionary mapped to the receiver's properties. 117 | Any invalid key-value pair in dictionary will be ignored. 118 | 119 | @discussion The key in `dictionary` will mapped to the reciever's property name, 120 | and the value will set to the property. If the value's type doesn't match the 121 | property, this method will try to convert the value based on these rules: 122 | 123 | `NSString`, `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger... 124 | `NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd". 125 | `NSString` -> NSURL. 126 | `NSValue` -> struct or union, such as CGRect, CGSize, ... 127 | `NSString` -> SEL, Class. 128 | 129 | @return Whether succeed. 130 | */ 131 | - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic; 132 | 133 | /** 134 | Generate a json object from the receiver's properties. 135 | 136 | @return A json object in `NSDictionary` or `NSArray`, or nil if an error occurs. 137 | See [NSJSONSerialization isValidJSONObject] for more information. 138 | 139 | @discussion Any of the invalid property is ignored. 140 | If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it just convert 141 | the inner object to json object. 142 | */ 143 | - (nullable id)yy_modelToJSONObject; 144 | 145 | /** 146 | Generate a json string's data from the receiver's properties. 147 | 148 | @return A json string's data, or nil if an error occurs. 149 | 150 | @discussion Any of the invalid property is ignored. 151 | If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the 152 | inner object to json string. 153 | */ 154 | - (nullable NSData *)yy_modelToJSONData; 155 | 156 | /** 157 | Generate a json string from the receiver's properties. 158 | 159 | @return A json string, or nil if an error occurs. 160 | 161 | @discussion Any of the invalid property is ignored. 162 | If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the 163 | inner object to json string. 164 | */ 165 | - (nullable NSString *)yy_modelToJSONString; 166 | 167 | /** 168 | Copy a instance with the receiver's properties. 169 | 170 | @return A copied instance, or nil if an error occurs. 171 | */ 172 | - (nullable id)yy_modelCopy; 173 | 174 | /** 175 | Encode the receiver's properties to a coder. 176 | 177 | @param aCoder An archiver object. 178 | */ 179 | - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder; 180 | 181 | /** 182 | Decode the receiver's properties from a decoder. 183 | 184 | @param aDecoder An archiver object. 185 | 186 | @return self 187 | */ 188 | - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder; 189 | 190 | /** 191 | Get a hash code with the receiver's properties. 192 | 193 | @return Hash code. 194 | */ 195 | - (NSUInteger)yy_modelHash; 196 | 197 | /** 198 | Compares the receiver with another object for equality, based on properties. 199 | 200 | @param model Another object. 201 | 202 | @return `YES` if the reciever is equal to the object, otherwise `NO`. 203 | */ 204 | - (BOOL)yy_modelIsEqual:(id)model; 205 | 206 | /** 207 | Description method for debugging purposes based on properties. 208 | 209 | @return A string that describes the contents of the receiver. 210 | */ 211 | - (NSString *)yy_modelDescription; 212 | 213 | @end 214 | 215 | 216 | 217 | /** 218 | Provide some data-model method for NSArray. 219 | */ 220 | @interface NSArray (YYModel) 221 | 222 | /** 223 | Creates and returns an array from a json-array. 224 | This method is thread-safe. 225 | 226 | @param cls The instance's class in array. 227 | @param json A json array of `NSArray`, `NSString` or `NSData`. 228 | Example: [{"name","Mary"},{name:"Joe"}] 229 | 230 | @return A array, or nil if an error occurs. 231 | */ 232 | + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json; 233 | 234 | @end 235 | 236 | 237 | 238 | /** 239 | Provide some data-model method for NSDictionary. 240 | */ 241 | @interface NSDictionary (YYModel) 242 | 243 | /** 244 | Creates and returns a dictionary from a json. 245 | This method is thread-safe. 246 | 247 | @param cls The value instance's class in dictionary. 248 | @param json A json dictionary of `NSDictionary`, `NSString` or `NSData`. 249 | Example: {"user1":{"name","Mary"}, "user2": {name:"Joe"}} 250 | 251 | @return A dictionary, or nil if an error occurs. 252 | */ 253 | + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; 254 | @end 255 | 256 | 257 | 258 | /** 259 | If the default model transform does not fit to your model class, implement one or 260 | more method in this protocol to change the default key-value transform process. 261 | There's no need to add '' to your class header. 262 | */ 263 | @protocol YYModel 264 | @optional 265 | 266 | /** 267 | Custom property mapper. 268 | 269 | @discussion If the key in JSON/Dictionary does not match to the model's property name, 270 | implements this method and returns the additional mapper. 271 | 272 | Example: 273 | 274 | json: 275 | { 276 | "n":"Harry Pottery", 277 | "p": 256, 278 | "ext" : { 279 | "desc" : "A book written by J.K.Rowling." 280 | }, 281 | "ID" : 100010 282 | } 283 | 284 | model: 285 | @interface YYBook : NSObject 286 | @property NSString *name; 287 | @property NSInteger page; 288 | @property NSString *desc; 289 | @property NSString *bookID; 290 | @end 291 | 292 | @implementation YYBook 293 | + (NSDictionary *)modelCustomPropertyMapper { 294 | return @{@"name" : @"n", 295 | @"page" : @"p", 296 | @"desc" : @"ext.desc", 297 | @"bookID": @[@"id", @"ID", @"book_id"]}; 298 | } 299 | @end 300 | 301 | @return A custom mapper for properties. 302 | */ 303 | + (nullable NSDictionary *)modelCustomPropertyMapper; 304 | 305 | /** 306 | The generic class mapper for container properties. 307 | 308 | @discussion If the property is a container object, such as NSArray/NSSet/NSDictionary, 309 | implements this method and returns a property->class mapper, tells which kind of 310 | object will be add to the array/set/dictionary. 311 | 312 | Example: 313 | @class YYShadow, YYBorder, YYAttachment; 314 | 315 | @interface YYAttributes 316 | @property NSString *name; 317 | @property NSArray *shadows; 318 | @property NSSet *borders; 319 | @property NSDictionary *attachments; 320 | @end 321 | 322 | @implementation YYAttributes 323 | + (NSDictionary *)modelContainerPropertyGenericClass { 324 | return @{@"shadows" : [YYShadow class], 325 | @"borders" : YYBorder.class, 326 | @"attachments" : @"YYAttachment" }; 327 | } 328 | @end 329 | 330 | @return A class mapper. 331 | */ 332 | + (nullable NSDictionary *)modelContainerPropertyGenericClass; 333 | 334 | /** 335 | If you need to create instances of different classes during json->object transform, 336 | use the method to choose custom class based on dictionary data. 337 | 338 | @discussion If the model implements this method, it will be called to determine resulting class 339 | during `+modelWithJSON:`, `+modelWithDictionary:`, conveting object of properties of parent objects 340 | (both singular and containers via `+modelContainerPropertyGenericClass`). 341 | 342 | Example: 343 | @class YYCircle, YYRectangle, YYLine; 344 | 345 | @implementation YYShape 346 | 347 | + (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary { 348 | if (dictionary[@"radius"] != nil) { 349 | return [YYCircle class]; 350 | } else if (dictionary[@"width"] != nil) { 351 | return [YYRectangle class]; 352 | } else if (dictionary[@"y2"] != nil) { 353 | return [YYLine class]; 354 | } else { 355 | return [self class]; 356 | } 357 | } 358 | 359 | @end 360 | 361 | @param dictionary The json/kv dictionary. 362 | 363 | @return Class to create from this dictionary, `nil` to use current class. 364 | 365 | */ 366 | + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary; 367 | 368 | /** 369 | All the properties in blacklist will be ignored in model transform process. 370 | Returns nil to ignore this feature. 371 | 372 | @return An array of property's name. 373 | */ 374 | + (nullable NSArray *)modelPropertyBlacklist; 375 | 376 | /** 377 | If a property is not in the whitelist, it will be ignored in model transform process. 378 | Returns nil to ignore this feature. 379 | 380 | @return An array of property's name. 381 | */ 382 | + (nullable NSArray *)modelPropertyWhitelist; 383 | 384 | /** 385 | This method's behavior is similar to `- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;`, 386 | but be called before the model transform. 387 | 388 | @discussion If the model implements this method, it will be called before 389 | `+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`. 390 | If this method returns nil, the transform process will ignore this model. 391 | 392 | @param dic The json/kv dictionary. 393 | 394 | @return Returns the modified dictionary, or nil to ignore this model. 395 | */ 396 | - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic; 397 | 398 | /** 399 | If the default json-to-model transform does not fit to your model object, implement 400 | this method to do additional process. You can also use this method to validate the 401 | model's properties. 402 | 403 | @discussion If the model implements this method, it will be called at the end of 404 | `+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`. 405 | If this method returns NO, the transform process will ignore this model. 406 | 407 | @param dic The json/kv dictionary. 408 | 409 | @return Returns YES if the model is valid, or NO to ignore this model. 410 | */ 411 | - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic; 412 | 413 | /** 414 | If the default model-to-json transform does not fit to your model class, implement 415 | this method to do additional process. You can also use this method to validate the 416 | json dictionary. 417 | 418 | @discussion If the model implements this method, it will be called at the end of 419 | `-modelToJSONObject` and `-modelToJSONString`. 420 | If this method returns NO, the transform process will ignore this json dictionary. 421 | 422 | @param dic The json dictionary. 423 | 424 | @return Returns YES if the model is valid, or NO to ignore this model. 425 | */ 426 | - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic; 427 | 428 | @end 429 | 430 | NS_ASSUME_NONNULL_END 431 | -------------------------------------------------------------------------------- /YYModel-Demo/YYModel/NSObject+YYModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+YYModel.m 3 | // YYModel 4 | // 5 | // Created by ibireme on 15/5/10. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "NSObject+YYModel.h" 13 | #import "YYClassInfo.h" 14 | #import 15 | 16 | #define force_inline __inline__ __attribute__((always_inline)) 17 | 18 | /// Foundation Class Type 19 | typedef NS_ENUM (NSUInteger, YYEncodingNSType) { 20 | YYEncodingTypeNSUnknown = 0, 21 | YYEncodingTypeNSString, 22 | YYEncodingTypeNSMutableString, 23 | YYEncodingTypeNSValue, 24 | YYEncodingTypeNSNumber, 25 | YYEncodingTypeNSDecimalNumber, 26 | YYEncodingTypeNSData, 27 | YYEncodingTypeNSMutableData, 28 | YYEncodingTypeNSDate, 29 | YYEncodingTypeNSURL, 30 | YYEncodingTypeNSArray, 31 | YYEncodingTypeNSMutableArray, 32 | YYEncodingTypeNSDictionary, 33 | YYEncodingTypeNSMutableDictionary, 34 | YYEncodingTypeNSSet, 35 | YYEncodingTypeNSMutableSet, 36 | }; 37 | 38 | /// Get the Foundation class type from property info. 39 | static force_inline YYEncodingNSType YYClassGetNSType(Class cls) { 40 | if (!cls) return YYEncodingTypeNSUnknown; 41 | if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString; 42 | if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString; 43 | if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber; 44 | if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber; 45 | if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue; 46 | if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData; 47 | if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData; 48 | if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate; 49 | if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL; 50 | if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray; 51 | if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray; 52 | if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary; 53 | if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary; 54 | if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet; 55 | if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet; 56 | return YYEncodingTypeNSUnknown; 57 | } 58 | 59 | /// Whether the type is c number. 60 | static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) { 61 | switch (type & YYEncodingTypeMask) { 62 | case YYEncodingTypeBool: 63 | case YYEncodingTypeInt8: 64 | case YYEncodingTypeUInt8: 65 | case YYEncodingTypeInt16: 66 | case YYEncodingTypeUInt16: 67 | case YYEncodingTypeInt32: 68 | case YYEncodingTypeUInt32: 69 | case YYEncodingTypeInt64: 70 | case YYEncodingTypeUInt64: 71 | case YYEncodingTypeFloat: 72 | case YYEncodingTypeDouble: 73 | case YYEncodingTypeLongDouble: return YES; 74 | default: return NO; 75 | } 76 | } 77 | 78 | /// Parse a number value from 'id'. 79 | static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) { 80 | static NSCharacterSet *dot; 81 | static NSDictionary *dic; 82 | static dispatch_once_t onceToken; 83 | dispatch_once(&onceToken, ^{ 84 | dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)]; 85 | dic = @{@"TRUE" : @(YES), 86 | @"True" : @(YES), 87 | @"true" : @(YES), 88 | @"FALSE" : @(NO), 89 | @"False" : @(NO), 90 | @"false" : @(NO), 91 | @"YES" : @(YES), 92 | @"Yes" : @(YES), 93 | @"yes" : @(YES), 94 | @"NO" : @(NO), 95 | @"No" : @(NO), 96 | @"no" : @(NO), 97 | @"NIL" : (id)kCFNull, 98 | @"Nil" : (id)kCFNull, 99 | @"nil" : (id)kCFNull, 100 | @"NULL" : (id)kCFNull, 101 | @"Null" : (id)kCFNull, 102 | @"null" : (id)kCFNull, 103 | @"(NULL)" : (id)kCFNull, 104 | @"(Null)" : (id)kCFNull, 105 | @"(null)" : (id)kCFNull, 106 | @"" : (id)kCFNull, 107 | @"" : (id)kCFNull, 108 | @"" : (id)kCFNull}; 109 | }); 110 | 111 | if (!value || value == (id)kCFNull) return nil; 112 | if ([value isKindOfClass:[NSNumber class]]) return value; 113 | if ([value isKindOfClass:[NSString class]]) { 114 | NSNumber *num = dic[value]; 115 | if (num) { 116 | if (num == (id)kCFNull) return nil; 117 | return num; 118 | } 119 | if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) { 120 | const char *cstring = ((NSString *)value).UTF8String; 121 | if (!cstring) return nil; 122 | double num = atof(cstring); 123 | if (isnan(num) || isinf(num)) return nil; 124 | return @(num); 125 | } else { 126 | const char *cstring = ((NSString *)value).UTF8String; 127 | if (!cstring) return nil; 128 | return @(atoll(cstring)); 129 | } 130 | } 131 | return nil; 132 | } 133 | 134 | /// Parse string to date. 135 | static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) { 136 | typedef NSDate* (^YYNSDateParseBlock)(NSString *string); 137 | #define kParserNum 34 138 | static YYNSDateParseBlock blocks[kParserNum + 1] = {0}; 139 | static dispatch_once_t onceToken; 140 | dispatch_once(&onceToken, ^{ 141 | { 142 | /* 143 | 2014-01-20 // Google 144 | */ 145 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 146 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 147 | formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 148 | formatter.dateFormat = @"yyyy-MM-dd"; 149 | blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; }; 150 | } 151 | 152 | { 153 | /* 154 | 2014-01-20 12:24:48 155 | 2014-01-20T12:24:48 // Google 156 | 2014-01-20 12:24:48.000 157 | 2014-01-20T12:24:48.000 158 | */ 159 | NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init]; 160 | formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 161 | formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 162 | formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss"; 163 | 164 | NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init]; 165 | formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 166 | formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 167 | formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss"; 168 | 169 | NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init]; 170 | formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 171 | formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 172 | formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS"; 173 | 174 | NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init]; 175 | formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 176 | formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; 177 | formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS"; 178 | 179 | blocks[19] = ^(NSString *string) { 180 | if ([string characterAtIndex:10] == 'T') { 181 | return [formatter1 dateFromString:string]; 182 | } else { 183 | return [formatter2 dateFromString:string]; 184 | } 185 | }; 186 | 187 | blocks[23] = ^(NSString *string) { 188 | if ([string characterAtIndex:10] == 'T') { 189 | return [formatter3 dateFromString:string]; 190 | } else { 191 | return [formatter4 dateFromString:string]; 192 | } 193 | }; 194 | } 195 | 196 | { 197 | /* 198 | 2014-01-20T12:24:48Z // Github, Apple 199 | 2014-01-20T12:24:48+0800 // Facebook 200 | 2014-01-20T12:24:48+12:00 // Google 201 | 2014-01-20T12:24:48.000Z 202 | 2014-01-20T12:24:48.000+0800 203 | 2014-01-20T12:24:48.000+12:00 204 | */ 205 | NSDateFormatter *formatter = [NSDateFormatter new]; 206 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 207 | formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; 208 | 209 | NSDateFormatter *formatter2 = [NSDateFormatter new]; 210 | formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 211 | formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 212 | 213 | blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; }; 214 | blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; }; 215 | blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; }; 216 | blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; 217 | blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; 218 | } 219 | 220 | { 221 | /* 222 | Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter 223 | Fri Sep 04 00:12:21.000 +0800 2015 224 | */ 225 | NSDateFormatter *formatter = [NSDateFormatter new]; 226 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 227 | formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; 228 | 229 | NSDateFormatter *formatter2 = [NSDateFormatter new]; 230 | formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 231 | formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy"; 232 | 233 | blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; }; 234 | blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; 235 | } 236 | }); 237 | if (!string) return nil; 238 | if (string.length > kParserNum) return nil; 239 | YYNSDateParseBlock parser = blocks[string.length]; 240 | if (!parser) return nil; 241 | return parser(string); 242 | #undef kParserNum 243 | } 244 | 245 | 246 | /// Get the 'NSBlock' class. 247 | static force_inline Class YYNSBlockClass() { 248 | static Class cls; 249 | static dispatch_once_t onceToken; 250 | dispatch_once(&onceToken, ^{ 251 | void (^block)(void) = ^{}; 252 | cls = ((NSObject *)block).class; 253 | while (class_getSuperclass(cls) != [NSObject class]) { 254 | cls = class_getSuperclass(cls); 255 | } 256 | }); 257 | return cls; // current is "NSBlock" 258 | } 259 | 260 | 261 | 262 | /** 263 | Get the ISO date formatter. 264 | 265 | ISO8601 format example: 266 | 2010-07-09T16:13:30+12:00 267 | 2011-01-11T11:11:11+0000 268 | 2011-01-26T19:06:43Z 269 | 270 | length: 20/24/25 271 | */ 272 | static force_inline NSDateFormatter *YYISODateFormatter() { 273 | static NSDateFormatter *formatter = nil; 274 | static dispatch_once_t onceToken; 275 | dispatch_once(&onceToken, ^{ 276 | formatter = [[NSDateFormatter alloc] init]; 277 | formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 278 | formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; 279 | }); 280 | return formatter; 281 | } 282 | 283 | /// Get the value with key paths from dictionary 284 | /// The dic should be NSDictionary, and the keyPath should not be nil. 285 | static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) { 286 | id value = nil; 287 | for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) { 288 | value = dic[keyPaths[i]]; 289 | if (i + 1 < max) { 290 | if ([value isKindOfClass:[NSDictionary class]]) { 291 | dic = value; 292 | } else { 293 | return nil; 294 | } 295 | } 296 | } 297 | return value; 298 | } 299 | 300 | /// Get the value with multi key (or key path) from dictionary 301 | /// The dic should be NSDictionary 302 | static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) { 303 | id value = nil; 304 | for (NSString *key in multiKeys) { 305 | if ([key isKindOfClass:[NSString class]]) { 306 | value = dic[key]; 307 | if (value) break; 308 | } else { 309 | value = YYValueForKeyPath(dic, (NSArray *)key); 310 | if (value) break; 311 | } 312 | } 313 | return value; 314 | } 315 | 316 | 317 | 318 | 319 | /// A property info in object model. 320 | @interface _YYModelPropertyMeta : NSObject { 321 | @package 322 | NSString *_name; ///< property's name 323 | YYEncodingType _type; ///< property's type 324 | YYEncodingNSType _nsType; ///< property's Foundation type 325 | BOOL _isCNumber; ///< is c number type 326 | Class _cls; ///< property's class, or nil 327 | Class _genericCls; ///< container's generic class, or nil if threr's no generic class 328 | SEL _getter; ///< getter, or nil if the instances cannot respond 329 | SEL _setter; ///< setter, or nil if the instances cannot respond 330 | BOOL _isKVCCompatible; ///< YES if it can access with key-value coding 331 | BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver 332 | BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary: 333 | 334 | /* 335 | property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil 336 | property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil 337 | property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array) 338 | */ 339 | NSString *_mappedToKey; ///< the key mapped to 340 | NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path) 341 | NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys) 342 | YYClassPropertyInfo *_info; ///< property's info 343 | _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key. 344 | } 345 | @end 346 | 347 | @implementation _YYModelPropertyMeta 348 | + (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic { 349 | 350 | // support pseudo generic class with protocol name 351 | if (!generic && propertyInfo.protocols) { 352 | for (NSString *protocol in propertyInfo.protocols) { 353 | Class cls = objc_getClass(protocol.UTF8String); 354 | if (cls) { 355 | generic = cls; 356 | break; 357 | } 358 | } 359 | } 360 | 361 | _YYModelPropertyMeta *meta = [self new]; 362 | meta->_name = propertyInfo.name; 363 | meta->_type = propertyInfo.type; 364 | meta->_info = propertyInfo; 365 | meta->_genericCls = generic; 366 | 367 | if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) { 368 | meta->_nsType = YYClassGetNSType(propertyInfo.cls); 369 | } else { 370 | meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type); 371 | } 372 | if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) { 373 | /* 374 | It seems that NSKeyedUnarchiver cannot decode NSValue except these structs: 375 | */ 376 | static NSSet *types = nil; 377 | static dispatch_once_t onceToken; 378 | dispatch_once(&onceToken, ^{ 379 | NSMutableSet *set = [NSMutableSet new]; 380 | // 32 bit 381 | [set addObject:@"{CGSize=ff}"]; 382 | [set addObject:@"{CGPoint=ff}"]; 383 | [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"]; 384 | [set addObject:@"{CGAffineTransform=ffffff}"]; 385 | [set addObject:@"{UIEdgeInsets=ffff}"]; 386 | [set addObject:@"{UIOffset=ff}"]; 387 | // 64 bit 388 | [set addObject:@"{CGSize=dd}"]; 389 | [set addObject:@"{CGPoint=dd}"]; 390 | [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"]; 391 | [set addObject:@"{CGAffineTransform=dddddd}"]; 392 | [set addObject:@"{UIEdgeInsets=dddd}"]; 393 | [set addObject:@"{UIOffset=dd}"]; 394 | types = set; 395 | }); 396 | if ([types containsObject:propertyInfo.typeEncoding]) { 397 | meta->_isStructAvailableForKeyedArchiver = YES; 398 | } 399 | } 400 | meta->_cls = propertyInfo.cls; 401 | 402 | if (generic) { 403 | meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)]; 404 | } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) { 405 | meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)]; 406 | } 407 | 408 | if (propertyInfo.getter) { 409 | if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) { 410 | meta->_getter = propertyInfo.getter; 411 | } 412 | } 413 | if (propertyInfo.setter) { 414 | if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) { 415 | meta->_setter = propertyInfo.setter; 416 | } 417 | } 418 | 419 | if (meta->_getter && meta->_setter) { 420 | /* 421 | KVC invalid type: 422 | long double 423 | pointer (such as SEL/CoreFoundation object) 424 | */ 425 | switch (meta->_type & YYEncodingTypeMask) { 426 | case YYEncodingTypeBool: 427 | case YYEncodingTypeInt8: 428 | case YYEncodingTypeUInt8: 429 | case YYEncodingTypeInt16: 430 | case YYEncodingTypeUInt16: 431 | case YYEncodingTypeInt32: 432 | case YYEncodingTypeUInt32: 433 | case YYEncodingTypeInt64: 434 | case YYEncodingTypeUInt64: 435 | case YYEncodingTypeFloat: 436 | case YYEncodingTypeDouble: 437 | case YYEncodingTypeObject: 438 | case YYEncodingTypeClass: 439 | case YYEncodingTypeBlock: 440 | case YYEncodingTypeStruct: 441 | case YYEncodingTypeUnion: { 442 | meta->_isKVCCompatible = YES; 443 | } break; 444 | default: break; 445 | } 446 | } 447 | 448 | return meta; 449 | } 450 | @end 451 | 452 | 453 | /// A class info in object model. 454 | @interface _YYModelMeta : NSObject { 455 | @package 456 | YYClassInfo *_classInfo; 457 | /// Key:mapped key and key path, Value:_YYModelPropertyMeta. 458 | NSDictionary *_mapper; 459 | /// Array<_YYModelPropertyMeta>, all property meta of this model. 460 | NSArray *_allPropertyMetas; 461 | /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path. 462 | NSArray *_keyPathPropertyMetas; 463 | /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys. 464 | NSArray *_multiKeysPropertyMetas; 465 | /// The number of mapped key (and key path), same to _mapper.count. 466 | NSUInteger _keyMappedCount; 467 | /// Model class type. 468 | YYEncodingNSType _nsType; 469 | 470 | BOOL _hasCustomWillTransformFromDictionary; 471 | BOOL _hasCustomTransformFromDictionary; 472 | BOOL _hasCustomTransformToDictionary; 473 | BOOL _hasCustomClassFromDictionary; 474 | } 475 | @end 476 | 477 | @implementation _YYModelMeta 478 | - (instancetype)initWithClass:(Class)cls { 479 | YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; 480 | if (!classInfo) return nil; 481 | self = [super init]; 482 | 483 | // Get black list 484 | NSSet *blacklist = nil; 485 | if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) { 486 | NSArray *properties = [(id)cls modelPropertyBlacklist]; 487 | if (properties) { 488 | blacklist = [NSSet setWithArray:properties]; 489 | } 490 | } 491 | 492 | // Get white list 493 | NSSet *whitelist = nil; 494 | if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) { 495 | NSArray *properties = [(id)cls modelPropertyWhitelist]; 496 | if (properties) { 497 | whitelist = [NSSet setWithArray:properties]; 498 | } 499 | } 500 | 501 | // Get container property's generic class 502 | NSDictionary *genericMapper = nil; 503 | if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) { 504 | genericMapper = [(id)cls modelContainerPropertyGenericClass]; 505 | if (genericMapper) { 506 | NSMutableDictionary *tmp = [NSMutableDictionary new]; 507 | [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 508 | if (![key isKindOfClass:[NSString class]]) return; 509 | Class meta = object_getClass(obj); 510 | if (!meta) return; 511 | if (class_isMetaClass(meta)) { 512 | tmp[key] = obj; 513 | } else if ([obj isKindOfClass:[NSString class]]) { 514 | Class cls = NSClassFromString(obj); 515 | if (cls) { 516 | tmp[key] = cls; 517 | } 518 | } 519 | }]; 520 | genericMapper = tmp; 521 | } 522 | } 523 | 524 | // Create all property metas. 525 | NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; 526 | YYClassInfo *curClassInfo = classInfo; 527 | while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy) 528 | for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) { 529 | if (!propertyInfo.name) continue; 530 | if (blacklist && [blacklist containsObject:propertyInfo.name]) continue; 531 | if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue; 532 | _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo 533 | propertyInfo:propertyInfo 534 | generic:genericMapper[propertyInfo.name]]; 535 | if (!meta || !meta->_name) continue; 536 | if (!meta->_getter || !meta->_setter) continue; 537 | if (allPropertyMetas[meta->_name]) continue; 538 | allPropertyMetas[meta->_name] = meta; 539 | } 540 | curClassInfo = curClassInfo.superClassInfo; 541 | } 542 | if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; 543 | 544 | // create mapper 545 | NSMutableDictionary *mapper = [NSMutableDictionary new]; 546 | NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; 547 | NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; 548 | 549 | if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { 550 | NSDictionary *customMapper = [(id )cls modelCustomPropertyMapper]; 551 | [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { 552 | _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; 553 | if (!propertyMeta) return; 554 | [allPropertyMetas removeObjectForKey:propertyName]; 555 | 556 | if ([mappedToKey isKindOfClass:[NSString class]]) { 557 | if (mappedToKey.length == 0) return; 558 | 559 | propertyMeta->_mappedToKey = mappedToKey; 560 | NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."]; 561 | for (NSString *onePath in keyPath) { 562 | if (onePath.length == 0) { 563 | NSMutableArray *tmp = keyPath.mutableCopy; 564 | [tmp removeObject:@""]; 565 | keyPath = tmp; 566 | break; 567 | } 568 | } 569 | if (keyPath.count > 1) { 570 | propertyMeta->_mappedToKeyPath = keyPath; 571 | [keyPathPropertyMetas addObject:propertyMeta]; 572 | } 573 | propertyMeta->_next = mapper[mappedToKey] ?: nil; 574 | mapper[mappedToKey] = propertyMeta; 575 | 576 | } else if ([mappedToKey isKindOfClass:[NSArray class]]) { 577 | 578 | NSMutableArray *mappedToKeyArray = [NSMutableArray new]; 579 | for (NSString *oneKey in ((NSArray *)mappedToKey)) { 580 | if (![oneKey isKindOfClass:[NSString class]]) continue; 581 | if (oneKey.length == 0) continue; 582 | 583 | NSArray *keyPath = [oneKey componentsSeparatedByString:@"."]; 584 | if (keyPath.count > 1) { 585 | [mappedToKeyArray addObject:keyPath]; 586 | } else { 587 | [mappedToKeyArray addObject:oneKey]; 588 | } 589 | 590 | if (!propertyMeta->_mappedToKey) { 591 | propertyMeta->_mappedToKey = oneKey; 592 | propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil; 593 | } 594 | } 595 | if (!propertyMeta->_mappedToKey) return; 596 | 597 | propertyMeta->_mappedToKeyArray = mappedToKeyArray; 598 | [multiKeysPropertyMetas addObject:propertyMeta]; 599 | 600 | propertyMeta->_next = mapper[mappedToKey] ?: nil; 601 | mapper[mappedToKey] = propertyMeta; 602 | } 603 | }]; 604 | } 605 | 606 | [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { 607 | propertyMeta->_mappedToKey = name; 608 | propertyMeta->_next = mapper[name] ?: nil; 609 | mapper[name] = propertyMeta; 610 | }]; 611 | 612 | if (mapper.count) _mapper = mapper; 613 | if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; 614 | if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; 615 | 616 | _classInfo = classInfo; 617 | _keyMappedCount = _allPropertyMetas.count; 618 | _nsType = YYClassGetNSType(cls); 619 | _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]); 620 | _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]); 621 | _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]); 622 | _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]); 623 | 624 | return self; 625 | } 626 | 627 | /// Returns the cached model class meta 628 | + (instancetype)metaWithClass:(Class)cls { 629 | if (!cls) return nil; 630 | static CFMutableDictionaryRef cache; 631 | static dispatch_once_t onceToken; 632 | static dispatch_semaphore_t lock; 633 | dispatch_once(&onceToken, ^{ 634 | cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 635 | lock = dispatch_semaphore_create(1); 636 | }); 637 | dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); 638 | _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); 639 | dispatch_semaphore_signal(lock); 640 | if (!meta || meta->_classInfo.needUpdate) { 641 | meta = [[_YYModelMeta alloc] initWithClass:cls]; 642 | if (meta) { 643 | dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); 644 | CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); 645 | dispatch_semaphore_signal(lock); 646 | } 647 | } 648 | return meta; 649 | } 650 | 651 | @end 652 | 653 | 654 | /** 655 | Get number from property. 656 | @discussion Caller should hold strong reference to the parameters before this function returns. 657 | @param model Should not be nil. 658 | @param meta Should not be nil, meta.isCNumber should be YES, meta.getter should not be nil. 659 | @return A number object, or nil if failed. 660 | */ 661 | static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model, 662 | __unsafe_unretained _YYModelPropertyMeta *meta) { 663 | switch (meta->_type & YYEncodingTypeMask) { 664 | case YYEncodingTypeBool: { 665 | return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 666 | } 667 | case YYEncodingTypeInt8: { 668 | return @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 669 | } 670 | case YYEncodingTypeUInt8: { 671 | return @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 672 | } 673 | case YYEncodingTypeInt16: { 674 | return @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 675 | } 676 | case YYEncodingTypeUInt16: { 677 | return @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 678 | } 679 | case YYEncodingTypeInt32: { 680 | return @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 681 | } 682 | case YYEncodingTypeUInt32: { 683 | return @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 684 | } 685 | case YYEncodingTypeInt64: { 686 | return @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 687 | } 688 | case YYEncodingTypeUInt64: { 689 | return @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); 690 | } 691 | case YYEncodingTypeFloat: { 692 | float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); 693 | if (isnan(num) || isinf(num)) return nil; 694 | return @(num); 695 | } 696 | case YYEncodingTypeDouble: { 697 | double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); 698 | if (isnan(num) || isinf(num)) return nil; 699 | return @(num); 700 | } 701 | case YYEncodingTypeLongDouble: { 702 | double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); 703 | if (isnan(num) || isinf(num)) return nil; 704 | return @(num); 705 | } 706 | default: return nil; 707 | } 708 | } 709 | 710 | /** 711 | Set number to property. 712 | @discussion Caller should hold strong reference to the parameters before this function returns. 713 | @param model Should not be nil. 714 | @param num Can be nil. 715 | @param meta Should not be nil, meta.isCNumber should be YES, meta.setter should not be nil. 716 | */ 717 | static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, 718 | __unsafe_unretained NSNumber *num, 719 | __unsafe_unretained _YYModelPropertyMeta *meta) { 720 | switch (meta->_type & YYEncodingTypeMask) { 721 | case YYEncodingTypeBool: { 722 | ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue); 723 | } break; 724 | case YYEncodingTypeInt8: { 725 | ((void (*)(id, SEL, int8_t))(void *) objc_msgSend)((id)model, meta->_setter, (int8_t)num.charValue); 726 | } break; 727 | case YYEncodingTypeUInt8: { 728 | ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint8_t)num.unsignedCharValue); 729 | } break; 730 | case YYEncodingTypeInt16: { 731 | ((void (*)(id, SEL, int16_t))(void *) objc_msgSend)((id)model, meta->_setter, (int16_t)num.shortValue); 732 | } break; 733 | case YYEncodingTypeUInt16: { 734 | ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint16_t)num.unsignedShortValue); 735 | } break; 736 | case YYEncodingTypeInt32: { 737 | ((void (*)(id, SEL, int32_t))(void *) objc_msgSend)((id)model, meta->_setter, (int32_t)num.intValue); 738 | } 739 | case YYEncodingTypeUInt32: { 740 | ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint32_t)num.unsignedIntValue); 741 | } break; 742 | case YYEncodingTypeInt64: { 743 | if ([num isKindOfClass:[NSDecimalNumber class]]) { 744 | ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue); 745 | } else { 746 | ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.longLongValue); 747 | } 748 | } break; 749 | case YYEncodingTypeUInt64: { 750 | if ([num isKindOfClass:[NSDecimalNumber class]]) { 751 | ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue); 752 | } else { 753 | ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue); 754 | } 755 | } break; 756 | case YYEncodingTypeFloat: { 757 | float f = num.floatValue; 758 | if (isnan(f) || isinf(f)) f = 0; 759 | ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f); 760 | } break; 761 | case YYEncodingTypeDouble: { 762 | double d = num.doubleValue; 763 | if (isnan(d) || isinf(d)) d = 0; 764 | ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)model, meta->_setter, d); 765 | } break; 766 | case YYEncodingTypeLongDouble: { 767 | long double d = num.doubleValue; 768 | if (isnan(d) || isinf(d)) d = 0; 769 | ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d); 770 | } // break; commented for code coverage in next line 771 | default: break; 772 | } 773 | } 774 | 775 | /** 776 | Set value to model with a property meta. 777 | 778 | @discussion Caller should hold strong reference to the parameters before this function returns. 779 | 780 | @param model Should not be nil. 781 | @param value Should not be nil, but can be NSNull. 782 | @param meta Should not be nil, and meta->_setter should not be nil. 783 | */ 784 | static void ModelSetValueForProperty(__unsafe_unretained id model, 785 | __unsafe_unretained id value, 786 | __unsafe_unretained _YYModelPropertyMeta *meta) { 787 | if (meta->_isCNumber) { 788 | NSNumber *num = YYNSNumberCreateFromID(value); 789 | ModelSetNumberToProperty(model, num, meta); 790 | if (num) [num class]; // hold the number 791 | } else if (meta->_nsType) { 792 | if (value == (id)kCFNull) { 793 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); 794 | } else { 795 | switch (meta->_nsType) { 796 | case YYEncodingTypeNSString: 797 | case YYEncodingTypeNSMutableString: { 798 | if ([value isKindOfClass:[NSString class]]) { 799 | if (meta->_nsType == YYEncodingTypeNSString) { 800 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 801 | } else { 802 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); 803 | } 804 | } else if ([value isKindOfClass:[NSNumber class]]) { 805 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 806 | meta->_setter, 807 | (meta->_nsType == YYEncodingTypeNSString) ? 808 | ((NSNumber *)value).stringValue : 809 | ((NSNumber *)value).stringValue.mutableCopy); 810 | } else if ([value isKindOfClass:[NSData class]]) { 811 | NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding]; 812 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string); 813 | } else if ([value isKindOfClass:[NSURL class]]) { 814 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 815 | meta->_setter, 816 | (meta->_nsType == YYEncodingTypeNSString) ? 817 | ((NSURL *)value).absoluteString : 818 | ((NSURL *)value).absoluteString.mutableCopy); 819 | } else if ([value isKindOfClass:[NSAttributedString class]]) { 820 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 821 | meta->_setter, 822 | (meta->_nsType == YYEncodingTypeNSString) ? 823 | ((NSAttributedString *)value).string : 824 | ((NSAttributedString *)value).string.mutableCopy); 825 | } 826 | } break; 827 | 828 | case YYEncodingTypeNSValue: 829 | case YYEncodingTypeNSNumber: 830 | case YYEncodingTypeNSDecimalNumber: { 831 | if (meta->_nsType == YYEncodingTypeNSNumber) { 832 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value)); 833 | } else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) { 834 | if ([value isKindOfClass:[NSDecimalNumber class]]) { 835 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 836 | } else if ([value isKindOfClass:[NSNumber class]]) { 837 | NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]]; 838 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum); 839 | } else if ([value isKindOfClass:[NSString class]]) { 840 | NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value]; 841 | NSDecimal dec = decNum.decimalValue; 842 | if (dec._length == 0 && dec._isNegative) { 843 | decNum = nil; // NaN 844 | } 845 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum); 846 | } 847 | } else { // YYEncodingTypeNSValue 848 | if ([value isKindOfClass:[NSValue class]]) { 849 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 850 | } 851 | } 852 | } break; 853 | 854 | case YYEncodingTypeNSData: 855 | case YYEncodingTypeNSMutableData: { 856 | if ([value isKindOfClass:[NSData class]]) { 857 | if (meta->_nsType == YYEncodingTypeNSData) { 858 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 859 | } else { 860 | NSMutableData *data = ((NSData *)value).mutableCopy; 861 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data); 862 | } 863 | } else if ([value isKindOfClass:[NSString class]]) { 864 | NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding]; 865 | if (meta->_nsType == YYEncodingTypeNSMutableData) { 866 | data = ((NSData *)data).mutableCopy; 867 | } 868 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data); 869 | } 870 | } break; 871 | 872 | case YYEncodingTypeNSDate: { 873 | if ([value isKindOfClass:[NSDate class]]) { 874 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 875 | } else if ([value isKindOfClass:[NSString class]]) { 876 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value)); 877 | } 878 | } break; 879 | 880 | case YYEncodingTypeNSURL: { 881 | if ([value isKindOfClass:[NSURL class]]) { 882 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 883 | } else if ([value isKindOfClass:[NSString class]]) { 884 | NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 885 | NSString *str = [value stringByTrimmingCharactersInSet:set]; 886 | if (str.length == 0) { 887 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil); 888 | } else { 889 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]); 890 | } 891 | } 892 | } break; 893 | 894 | case YYEncodingTypeNSArray: 895 | case YYEncodingTypeNSMutableArray: { 896 | if (meta->_genericCls) { 897 | NSArray *valueArr = nil; 898 | if ([value isKindOfClass:[NSArray class]]) valueArr = value; 899 | else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects; 900 | if (valueArr) { 901 | NSMutableArray *objectArr = [NSMutableArray new]; 902 | for (id one in valueArr) { 903 | if ([one isKindOfClass:meta->_genericCls]) { 904 | [objectArr addObject:one]; 905 | } else if ([one isKindOfClass:[NSDictionary class]]) { 906 | Class cls = meta->_genericCls; 907 | if (meta->_hasCustomClassFromDictionary) { 908 | cls = [cls modelCustomClassForDictionary:one]; 909 | if (!cls) cls = meta->_genericCls; // for xcode code coverage 910 | } 911 | NSObject *newOne = [cls new]; 912 | [newOne yy_modelSetWithDictionary:one]; 913 | if (newOne) [objectArr addObject:newOne]; 914 | } 915 | } 916 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr); 917 | } 918 | } else { 919 | if ([value isKindOfClass:[NSArray class]]) { 920 | if (meta->_nsType == YYEncodingTypeNSArray) { 921 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 922 | } else { 923 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 924 | meta->_setter, 925 | ((NSArray *)value).mutableCopy); 926 | } 927 | } else if ([value isKindOfClass:[NSSet class]]) { 928 | if (meta->_nsType == YYEncodingTypeNSArray) { 929 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects); 930 | } else { 931 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 932 | meta->_setter, 933 | ((NSSet *)value).allObjects.mutableCopy); 934 | } 935 | } 936 | } 937 | } break; 938 | 939 | case YYEncodingTypeNSDictionary: 940 | case YYEncodingTypeNSMutableDictionary: { 941 | if ([value isKindOfClass:[NSDictionary class]]) { 942 | if (meta->_genericCls) { 943 | NSMutableDictionary *dic = [NSMutableDictionary new]; 944 | [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) { 945 | if ([oneValue isKindOfClass:[NSDictionary class]]) { 946 | Class cls = meta->_genericCls; 947 | if (meta->_hasCustomClassFromDictionary) { 948 | cls = [cls modelCustomClassForDictionary:oneValue]; 949 | if (!cls) cls = meta->_genericCls; // for xcode code coverage 950 | } 951 | NSObject *newOne = [cls new]; 952 | [newOne yy_modelSetWithDictionary:(id)oneValue]; 953 | if (newOne) dic[oneKey] = newOne; 954 | } 955 | }]; 956 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic); 957 | } else { 958 | if (meta->_nsType == YYEncodingTypeNSDictionary) { 959 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); 960 | } else { 961 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 962 | meta->_setter, 963 | ((NSDictionary *)value).mutableCopy); 964 | } 965 | } 966 | } 967 | } break; 968 | 969 | case YYEncodingTypeNSSet: 970 | case YYEncodingTypeNSMutableSet: { 971 | NSSet *valueSet = nil; 972 | if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value]; 973 | else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value); 974 | 975 | if (meta->_genericCls) { 976 | NSMutableSet *set = [NSMutableSet new]; 977 | for (id one in valueSet) { 978 | if ([one isKindOfClass:meta->_genericCls]) { 979 | [set addObject:one]; 980 | } else if ([one isKindOfClass:[NSDictionary class]]) { 981 | Class cls = meta->_genericCls; 982 | if (meta->_hasCustomClassFromDictionary) { 983 | cls = [cls modelCustomClassForDictionary:one]; 984 | if (!cls) cls = meta->_genericCls; // for xcode code coverage 985 | } 986 | NSObject *newOne = [cls new]; 987 | [newOne yy_modelSetWithDictionary:one]; 988 | if (newOne) [set addObject:newOne]; 989 | } 990 | } 991 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set); 992 | } else { 993 | if (meta->_nsType == YYEncodingTypeNSSet) { 994 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet); 995 | } else { 996 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, 997 | meta->_setter, 998 | ((NSSet *)valueSet).mutableCopy); 999 | } 1000 | } 1001 | } // break; commented for code coverage in next line 1002 | 1003 | default: break; 1004 | } 1005 | } 1006 | } else { 1007 | BOOL isNull = (value == (id)kCFNull); 1008 | switch (meta->_type & YYEncodingTypeMask) { 1009 | case YYEncodingTypeObject: { 1010 | if (isNull) { 1011 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); 1012 | } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) { 1013 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); 1014 | } else if ([value isKindOfClass:[NSDictionary class]]) { 1015 | NSObject *one = nil; 1016 | if (meta->_getter) { 1017 | one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); 1018 | } 1019 | if (one) { 1020 | [one yy_modelSetWithDictionary:value]; 1021 | } else { 1022 | Class cls = meta->_cls; 1023 | if (meta->_hasCustomClassFromDictionary) { 1024 | cls = [cls modelCustomClassForDictionary:value]; 1025 | if (!cls) cls = meta->_genericCls; // for xcode code coverage 1026 | } 1027 | one = [cls new]; 1028 | [one yy_modelSetWithDictionary:value]; 1029 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); 1030 | } 1031 | } 1032 | } break; 1033 | 1034 | case YYEncodingTypeClass: { 1035 | if (isNull) { 1036 | ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL); 1037 | } else { 1038 | Class cls = nil; 1039 | if ([value isKindOfClass:[NSString class]]) { 1040 | cls = NSClassFromString(value); 1041 | if (cls) { 1042 | ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls); 1043 | } 1044 | } else { 1045 | cls = object_getClass(value); 1046 | if (cls) { 1047 | if (class_isMetaClass(cls)) { 1048 | ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value); 1049 | } 1050 | } 1051 | } 1052 | } 1053 | } break; 1054 | 1055 | case YYEncodingTypeSEL: { 1056 | if (isNull) { 1057 | ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL); 1058 | } else if ([value isKindOfClass:[NSString class]]) { 1059 | SEL sel = NSSelectorFromString(value); 1060 | if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel); 1061 | } 1062 | } break; 1063 | 1064 | case YYEncodingTypeBlock: { 1065 | if (isNull) { 1066 | ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL); 1067 | } else if ([value isKindOfClass:YYNSBlockClass()]) { 1068 | ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value); 1069 | } 1070 | } break; 1071 | 1072 | case YYEncodingTypeStruct: 1073 | case YYEncodingTypeUnion: 1074 | case YYEncodingTypeCArray: { 1075 | if ([value isKindOfClass:[NSValue class]]) { 1076 | const char *valueType = ((NSValue *)value).objCType; 1077 | const char *metaType = meta->_info.typeEncoding.UTF8String; 1078 | if (valueType && metaType && strcmp(valueType, metaType) == 0) { 1079 | [model setValue:value forKey:meta->_name]; 1080 | } 1081 | } 1082 | } break; 1083 | 1084 | case YYEncodingTypePointer: 1085 | case YYEncodingTypeCString: { 1086 | if (isNull) { 1087 | ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL); 1088 | } else if ([value isKindOfClass:[NSValue class]]) { 1089 | NSValue *nsValue = value; 1090 | if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) { 1091 | ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue); 1092 | } 1093 | } 1094 | } // break; commented for code coverage in next line 1095 | 1096 | default: break; 1097 | } 1098 | } 1099 | } 1100 | 1101 | 1102 | typedef struct { 1103 | void *modelMeta; ///< _YYModelMeta 1104 | void *model; ///< id (self) 1105 | void *dictionary; ///< NSDictionary (json) 1106 | } ModelSetContext; 1107 | 1108 | /** 1109 | Apply function for dictionary, to set the key-value pair to model. 1110 | 1111 | @param _key should not be nil, NSString. 1112 | @param _value should not be nil. 1113 | @param _context _context.modelMeta and _context.model should not be nil. 1114 | */ 1115 | static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { 1116 | ModelSetContext *context = _context; 1117 | __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); 1118 | __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; 1119 | __unsafe_unretained id model = (__bridge id)(context->model); 1120 | while (propertyMeta) { 1121 | if (propertyMeta->_setter) { 1122 | ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); 1123 | } 1124 | propertyMeta = propertyMeta->_next; 1125 | }; 1126 | } 1127 | 1128 | /** 1129 | Apply function for model property meta, to set dictionary to model. 1130 | 1131 | @param _propertyMeta should not be nil, _YYModelPropertyMeta. 1132 | @param _context _context.model and _context.dictionary should not be nil. 1133 | */ 1134 | static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { 1135 | ModelSetContext *context = _context; 1136 | __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary); 1137 | __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta); 1138 | if (!propertyMeta->_setter) return; 1139 | id value = nil; 1140 | 1141 | if (propertyMeta->_mappedToKeyArray) { 1142 | value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); 1143 | } else if (propertyMeta->_mappedToKeyPath) { 1144 | value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath); 1145 | } else { 1146 | value = [dictionary objectForKey:propertyMeta->_mappedToKey]; 1147 | } 1148 | 1149 | if (value) { 1150 | __unsafe_unretained id model = (__bridge id)(context->model); 1151 | ModelSetValueForProperty(model, value, propertyMeta); 1152 | } 1153 | } 1154 | 1155 | /** 1156 | Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull), 1157 | or nil if an error occurs. 1158 | 1159 | @param model Model, can be nil. 1160 | @return JSON object, nil if an error occurs. 1161 | */ 1162 | static id ModelToJSONObjectRecursive(NSObject *model) { 1163 | if (!model || model == (id)kCFNull) return model; 1164 | if ([model isKindOfClass:[NSString class]]) return model; 1165 | if ([model isKindOfClass:[NSNumber class]]) return model; 1166 | if ([model isKindOfClass:[NSDictionary class]]) { 1167 | if ([NSJSONSerialization isValidJSONObject:model]) return model; 1168 | NSMutableDictionary *newDic = [NSMutableDictionary new]; 1169 | [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 1170 | NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description; 1171 | if (!stringKey) return; 1172 | id jsonObj = ModelToJSONObjectRecursive(obj); 1173 | if (!jsonObj) jsonObj = (id)kCFNull; 1174 | newDic[stringKey] = jsonObj; 1175 | }]; 1176 | return newDic; 1177 | } 1178 | if ([model isKindOfClass:[NSSet class]]) { 1179 | NSArray *array = ((NSSet *)model).allObjects; 1180 | if ([NSJSONSerialization isValidJSONObject:array]) return array; 1181 | NSMutableArray *newArray = [NSMutableArray new]; 1182 | for (id obj in array) { 1183 | if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { 1184 | [newArray addObject:obj]; 1185 | } else { 1186 | id jsonObj = ModelToJSONObjectRecursive(obj); 1187 | if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; 1188 | } 1189 | } 1190 | return newArray; 1191 | } 1192 | if ([model isKindOfClass:[NSArray class]]) { 1193 | if ([NSJSONSerialization isValidJSONObject:model]) return model; 1194 | NSMutableArray *newArray = [NSMutableArray new]; 1195 | for (id obj in (NSArray *)model) { 1196 | if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { 1197 | [newArray addObject:obj]; 1198 | } else { 1199 | id jsonObj = ModelToJSONObjectRecursive(obj); 1200 | if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; 1201 | } 1202 | } 1203 | return newArray; 1204 | } 1205 | if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString; 1206 | if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string; 1207 | if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model]; 1208 | if ([model isKindOfClass:[NSData class]]) return nil; 1209 | 1210 | 1211 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]]; 1212 | if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil; 1213 | NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64]; 1214 | __unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block 1215 | [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { 1216 | if (!propertyMeta->_getter) return; 1217 | 1218 | id value = nil; 1219 | if (propertyMeta->_isCNumber) { 1220 | value = ModelCreateNumberFromProperty(model, propertyMeta); 1221 | } else if (propertyMeta->_nsType) { 1222 | id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); 1223 | value = ModelToJSONObjectRecursive(v); 1224 | } else { 1225 | switch (propertyMeta->_type & YYEncodingTypeMask) { 1226 | case YYEncodingTypeObject: { 1227 | id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); 1228 | value = ModelToJSONObjectRecursive(v); 1229 | if (value == (id)kCFNull) value = nil; 1230 | } break; 1231 | case YYEncodingTypeClass: { 1232 | Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); 1233 | value = v ? NSStringFromClass(v) : nil; 1234 | } break; 1235 | case YYEncodingTypeSEL: { 1236 | SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); 1237 | value = v ? NSStringFromSelector(v) : nil; 1238 | } break; 1239 | default: break; 1240 | } 1241 | } 1242 | if (!value) return; 1243 | 1244 | if (propertyMeta->_mappedToKeyPath) { 1245 | NSMutableDictionary *superDic = dic; 1246 | NSMutableDictionary *subDic = nil; 1247 | for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) { 1248 | NSString *key = propertyMeta->_mappedToKeyPath[i]; 1249 | if (i + 1 == max) { // end 1250 | if (!superDic[key]) superDic[key] = value; 1251 | break; 1252 | } 1253 | 1254 | subDic = superDic[key]; 1255 | if (subDic) { 1256 | if ([subDic isKindOfClass:[NSDictionary class]]) { 1257 | subDic = subDic.mutableCopy; 1258 | superDic[key] = subDic; 1259 | } else { 1260 | break; 1261 | } 1262 | } else { 1263 | subDic = [NSMutableDictionary new]; 1264 | superDic[key] = subDic; 1265 | } 1266 | superDic = subDic; 1267 | subDic = nil; 1268 | } 1269 | } else { 1270 | if (!dic[propertyMeta->_mappedToKey]) { 1271 | dic[propertyMeta->_mappedToKey] = value; 1272 | } 1273 | } 1274 | }]; 1275 | 1276 | if (modelMeta->_hasCustomTransformToDictionary) { 1277 | BOOL suc = [((id)model) modelCustomTransformToDictionary:dic]; 1278 | if (!suc) return nil; 1279 | } 1280 | return result; 1281 | } 1282 | 1283 | /// Add indent to string (exclude first line) 1284 | static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) { 1285 | for (NSUInteger i = 0, max = desc.length; i < max; i++) { 1286 | unichar c = [desc characterAtIndex:i]; 1287 | if (c == '\n') { 1288 | for (NSUInteger j = 0; j < indent; j++) { 1289 | [desc insertString:@" " atIndex:i + 1]; 1290 | } 1291 | i += indent * 4; 1292 | max += indent * 4; 1293 | } 1294 | } 1295 | return desc; 1296 | } 1297 | 1298 | /// Generaate a description string 1299 | static NSString *ModelDescription(NSObject *model) { 1300 | static const int kDescMaxLength = 100; 1301 | if (!model) return @""; 1302 | if (model == (id)kCFNull) return @""; 1303 | if (![model isKindOfClass:[NSObject class]]) return [NSString stringWithFormat:@"%@",model]; 1304 | 1305 | 1306 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:model.class]; 1307 | switch (modelMeta->_nsType) { 1308 | case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { 1309 | return [NSString stringWithFormat:@"\"%@\"",model]; 1310 | } 1311 | 1312 | case YYEncodingTypeNSValue: 1313 | case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: { 1314 | NSString *tmp = model.description; 1315 | if (tmp.length > kDescMaxLength) { 1316 | tmp = [tmp substringToIndex:kDescMaxLength]; 1317 | tmp = [tmp stringByAppendingString:@"..."]; 1318 | } 1319 | return tmp; 1320 | } 1321 | 1322 | case YYEncodingTypeNSNumber: 1323 | case YYEncodingTypeNSDecimalNumber: 1324 | case YYEncodingTypeNSDate: 1325 | case YYEncodingTypeNSURL: { 1326 | return [NSString stringWithFormat:@"%@",model]; 1327 | } 1328 | 1329 | case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: { 1330 | model = ((NSSet *)model).allObjects; 1331 | } // no break 1332 | 1333 | case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: { 1334 | NSArray *array = (id)model; 1335 | NSMutableString *desc = [NSMutableString new]; 1336 | if (array.count == 0) { 1337 | return [desc stringByAppendingString:@"[]"]; 1338 | } else { 1339 | [desc appendFormat:@"[\n"]; 1340 | for (NSUInteger i = 0, max = array.count; i < max; i++) { 1341 | NSObject *obj = array[i]; 1342 | [desc appendString:@" "]; 1343 | [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)]; 1344 | [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; 1345 | } 1346 | [desc appendString:@"]"]; 1347 | return desc; 1348 | } 1349 | } 1350 | case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: { 1351 | NSDictionary *dic = (id)model; 1352 | NSMutableString *desc = [NSMutableString new]; 1353 | if (dic.count == 0) { 1354 | return [desc stringByAppendingString:@"{}"]; 1355 | } else { 1356 | NSArray *keys = dic.allKeys; 1357 | 1358 | [desc appendFormat:@"{\n"]; 1359 | for (NSUInteger i = 0, max = keys.count; i < max; i++) { 1360 | NSString *key = keys[i]; 1361 | NSObject *value = dic[key]; 1362 | [desc appendString:@" "]; 1363 | [desc appendFormat:@"%@ = %@",key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, 1)]; 1364 | [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; 1365 | } 1366 | [desc appendString:@"}"]; 1367 | } 1368 | return desc; 1369 | } 1370 | 1371 | default: { 1372 | NSMutableString *desc = [NSMutableString new]; 1373 | [desc appendFormat:@"<%@: %p>", model.class, model]; 1374 | if (modelMeta->_allPropertyMetas.count == 0) return desc; 1375 | 1376 | // sort property names 1377 | NSArray *properties = [modelMeta->_allPropertyMetas 1378 | sortedArrayUsingComparator:^NSComparisonResult(_YYModelPropertyMeta *p1, _YYModelPropertyMeta *p2) { 1379 | return [p1->_name compare:p2->_name]; 1380 | }]; 1381 | 1382 | [desc appendFormat:@" {\n"]; 1383 | for (NSUInteger i = 0, max = properties.count; i < max; i++) { 1384 | _YYModelPropertyMeta *property = properties[i]; 1385 | NSString *propertyDesc; 1386 | if (property->_isCNumber) { 1387 | NSNumber *num = ModelCreateNumberFromProperty(model, property); 1388 | propertyDesc = num.stringValue; 1389 | } else { 1390 | switch (property->_type & YYEncodingTypeMask) { 1391 | case YYEncodingTypeObject: { 1392 | id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); 1393 | propertyDesc = ModelDescription(v); 1394 | if (!propertyDesc) propertyDesc = @""; 1395 | } break; 1396 | case YYEncodingTypeClass: { 1397 | id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); 1398 | propertyDesc = ((NSObject *)v).description; 1399 | if (!propertyDesc) propertyDesc = @""; 1400 | } break; 1401 | case YYEncodingTypeSEL: { 1402 | SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); 1403 | if (sel) propertyDesc = NSStringFromSelector(sel); 1404 | else propertyDesc = @""; 1405 | } break; 1406 | case YYEncodingTypeBlock: { 1407 | id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); 1408 | propertyDesc = block ? ((NSObject *)block).description : @""; 1409 | } break; 1410 | case YYEncodingTypeCArray: case YYEncodingTypeCString: case YYEncodingTypePointer: { 1411 | void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); 1412 | propertyDesc = [NSString stringWithFormat:@"%p",pointer]; 1413 | } break; 1414 | case YYEncodingTypeStruct: case YYEncodingTypeUnion: { 1415 | NSValue *value = [model valueForKey:property->_name]; 1416 | propertyDesc = value ? value.description : @"{unknown}"; 1417 | } break; 1418 | default: propertyDesc = @""; 1419 | } 1420 | } 1421 | 1422 | propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, 1); 1423 | [desc appendFormat:@" %@ = %@",property->_name, propertyDesc]; 1424 | [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; 1425 | } 1426 | [desc appendFormat:@"}"]; 1427 | return desc; 1428 | } 1429 | } 1430 | } 1431 | 1432 | 1433 | @implementation NSObject (YYModel) 1434 | 1435 | + (NSDictionary *)_yy_dictionaryWithJSON:(id)json { 1436 | if (!json || json == (id)kCFNull) return nil; 1437 | NSDictionary *dic = nil; 1438 | NSData *jsonData = nil; 1439 | if ([json isKindOfClass:[NSDictionary class]]) { 1440 | dic = json; 1441 | } else if ([json isKindOfClass:[NSString class]]) { 1442 | jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; 1443 | } else if ([json isKindOfClass:[NSData class]]) { 1444 | jsonData = json; 1445 | } 1446 | if (jsonData) { 1447 | dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; 1448 | if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; 1449 | } 1450 | return dic; 1451 | } 1452 | 1453 | + (instancetype)yy_modelWithJSON:(id)json { 1454 | NSDictionary *dic = [self _yy_dictionaryWithJSON:json]; 1455 | return [self yy_modelWithDictionary:dic]; 1456 | } 1457 | 1458 | + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { 1459 | if (!dictionary || dictionary == (id)kCFNull) return nil; 1460 | if (![dictionary isKindOfClass:[NSDictionary class]]) return nil; 1461 | 1462 | Class cls = [self class]; 1463 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; 1464 | if (modelMeta->_hasCustomClassFromDictionary) { 1465 | cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; 1466 | } 1467 | 1468 | NSObject *one = [cls new]; 1469 | if ([one yy_modelSetWithDictionary:dictionary]) return one; 1470 | return nil; 1471 | } 1472 | 1473 | - (BOOL)yy_modelSetWithJSON:(id)json { 1474 | NSDictionary *dic = [NSObject _yy_dictionaryWithJSON:json]; 1475 | return [self yy_modelSetWithDictionary:dic]; 1476 | } 1477 | 1478 | - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { 1479 | if (!dic || dic == (id)kCFNull) return NO; 1480 | if (![dic isKindOfClass:[NSDictionary class]]) return NO; 1481 | 1482 | 1483 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; 1484 | if (modelMeta->_keyMappedCount == 0) return NO; 1485 | 1486 | if (modelMeta->_hasCustomWillTransformFromDictionary) { 1487 | dic = [((id)self) modelCustomWillTransformFromDictionary:dic]; 1488 | if (![dic isKindOfClass:[NSDictionary class]]) return NO; 1489 | } 1490 | 1491 | ModelSetContext context = {0}; 1492 | context.modelMeta = (__bridge void *)(modelMeta); 1493 | context.model = (__bridge void *)(self); 1494 | context.dictionary = (__bridge void *)(dic); 1495 | 1496 | 1497 | if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { 1498 | CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); 1499 | if (modelMeta->_keyPathPropertyMetas) { 1500 | CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, 1501 | CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), 1502 | ModelSetWithPropertyMetaArrayFunction, 1503 | &context); 1504 | } 1505 | if (modelMeta->_multiKeysPropertyMetas) { 1506 | CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, 1507 | CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), 1508 | ModelSetWithPropertyMetaArrayFunction, 1509 | &context); 1510 | } 1511 | } else { 1512 | CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, 1513 | CFRangeMake(0, modelMeta->_keyMappedCount), 1514 | ModelSetWithPropertyMetaArrayFunction, 1515 | &context); 1516 | } 1517 | 1518 | if (modelMeta->_hasCustomTransformFromDictionary) { 1519 | return [((id)self) modelCustomTransformFromDictionary:dic]; 1520 | } 1521 | return YES; 1522 | } 1523 | 1524 | - (id)yy_modelToJSONObject { 1525 | /* 1526 | Apple said: 1527 | The top level object is an NSArray or NSDictionary. 1528 | All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull. 1529 | All dictionary keys are instances of NSString. 1530 | Numbers are not NaN or infinity. 1531 | */ 1532 | id jsonObject = ModelToJSONObjectRecursive(self); 1533 | if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject; 1534 | if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject; 1535 | return nil; 1536 | } 1537 | 1538 | - (NSData *)yy_modelToJSONData { 1539 | id jsonObject = [self yy_modelToJSONObject]; 1540 | if (!jsonObject) return nil; 1541 | return [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:NULL]; 1542 | } 1543 | 1544 | - (NSString *)yy_modelToJSONString { 1545 | NSData *jsonData = [self yy_modelToJSONData]; 1546 | if (jsonData.length == 0) return nil; 1547 | return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 1548 | } 1549 | 1550 | - (id)yy_modelCopy{ 1551 | if (self == (id)kCFNull) return self; 1552 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; 1553 | if (modelMeta->_nsType) return [self copy]; 1554 | 1555 | NSObject *one = [self.class new]; 1556 | for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { 1557 | if (!propertyMeta->_getter || !propertyMeta->_setter) continue; 1558 | 1559 | if (propertyMeta->_isCNumber) { 1560 | switch (propertyMeta->_type & YYEncodingTypeMask) { 1561 | case YYEncodingTypeBool: { 1562 | bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1563 | ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1564 | } break; 1565 | case YYEncodingTypeInt8: 1566 | case YYEncodingTypeUInt8: { 1567 | uint8_t num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1568 | ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1569 | } break; 1570 | case YYEncodingTypeInt16: 1571 | case YYEncodingTypeUInt16: { 1572 | uint16_t num = ((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1573 | ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1574 | } break; 1575 | case YYEncodingTypeInt32: 1576 | case YYEncodingTypeUInt32: { 1577 | uint32_t num = ((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1578 | ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1579 | } break; 1580 | case YYEncodingTypeInt64: 1581 | case YYEncodingTypeUInt64: { 1582 | uint64_t num = ((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1583 | ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1584 | } break; 1585 | case YYEncodingTypeFloat: { 1586 | float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1587 | ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1588 | } break; 1589 | case YYEncodingTypeDouble: { 1590 | double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1591 | ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1592 | } break; 1593 | case YYEncodingTypeLongDouble: { 1594 | long double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1595 | ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); 1596 | } // break; commented for code coverage in next line 1597 | default: break; 1598 | } 1599 | } else { 1600 | switch (propertyMeta->_type & YYEncodingTypeMask) { 1601 | case YYEncodingTypeObject: 1602 | case YYEncodingTypeClass: 1603 | case YYEncodingTypeBlock: { 1604 | id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1605 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value); 1606 | } break; 1607 | case YYEncodingTypeSEL: 1608 | case YYEncodingTypePointer: 1609 | case YYEncodingTypeCString: { 1610 | size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); 1611 | ((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value); 1612 | } break; 1613 | case YYEncodingTypeStruct: 1614 | case YYEncodingTypeUnion: { 1615 | @try { 1616 | NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; 1617 | if (value) { 1618 | [one setValue:value forKey:propertyMeta->_name]; 1619 | } 1620 | } @catch (NSException *exception) {} 1621 | } // break; commented for code coverage in next line 1622 | default: break; 1623 | } 1624 | } 1625 | } 1626 | return one; 1627 | } 1628 | 1629 | - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder { 1630 | if (!aCoder) return; 1631 | if (self == (id)kCFNull) { 1632 | [((id)self)encodeWithCoder:aCoder]; 1633 | return; 1634 | } 1635 | 1636 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; 1637 | if (modelMeta->_nsType) { 1638 | [((id)self)encodeWithCoder:aCoder]; 1639 | return; 1640 | } 1641 | 1642 | for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { 1643 | if (!propertyMeta->_getter) return; 1644 | 1645 | if (propertyMeta->_isCNumber) { 1646 | NSNumber *value = ModelCreateNumberFromProperty(self, propertyMeta); 1647 | if (value) [aCoder encodeObject:value forKey:propertyMeta->_name]; 1648 | } else { 1649 | switch (propertyMeta->_type & YYEncodingTypeMask) { 1650 | case YYEncodingTypeObject: { 1651 | id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter); 1652 | if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) { 1653 | if ([value isKindOfClass:[NSValue class]]) { 1654 | if ([value isKindOfClass:[NSNumber class]]) { 1655 | [aCoder encodeObject:value forKey:propertyMeta->_name]; 1656 | } 1657 | } else { 1658 | [aCoder encodeObject:value forKey:propertyMeta->_name]; 1659 | } 1660 | } 1661 | } break; 1662 | case YYEncodingTypeSEL: { 1663 | SEL value = ((SEL (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter); 1664 | if (value) { 1665 | NSString *str = NSStringFromSelector(value); 1666 | [aCoder encodeObject:str forKey:propertyMeta->_name]; 1667 | } 1668 | } break; 1669 | case YYEncodingTypeStruct: 1670 | case YYEncodingTypeUnion: { 1671 | if (propertyMeta->_isKVCCompatible && propertyMeta->_isStructAvailableForKeyedArchiver) { 1672 | @try { 1673 | NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; 1674 | [aCoder encodeObject:value forKey:propertyMeta->_name]; 1675 | } @catch (NSException *exception) {} 1676 | } 1677 | } break; 1678 | 1679 | default: 1680 | break; 1681 | } 1682 | } 1683 | } 1684 | } 1685 | 1686 | - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder { 1687 | if (!aDecoder) return self; 1688 | if (self == (id)kCFNull) return self; 1689 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; 1690 | if (modelMeta->_nsType) return self; 1691 | 1692 | for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { 1693 | if (!propertyMeta->_setter) continue; 1694 | 1695 | if (propertyMeta->_isCNumber) { 1696 | NSNumber *value = [aDecoder decodeObjectForKey:propertyMeta->_name]; 1697 | if ([value isKindOfClass:[NSNumber class]]) { 1698 | ModelSetNumberToProperty(self, value, propertyMeta); 1699 | [value class]; 1700 | } 1701 | } else { 1702 | YYEncodingType type = propertyMeta->_type & YYEncodingTypeMask; 1703 | switch (type) { 1704 | case YYEncodingTypeObject: { 1705 | id value = [aDecoder decodeObjectForKey:propertyMeta->_name]; 1706 | ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)self, propertyMeta->_setter, value); 1707 | } break; 1708 | case YYEncodingTypeSEL: { 1709 | NSString *str = [aDecoder decodeObjectForKey:propertyMeta->_name]; 1710 | if ([str isKindOfClass:[NSString class]]) { 1711 | SEL sel = NSSelectorFromString(str); 1712 | ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_setter, sel); 1713 | } 1714 | } break; 1715 | case YYEncodingTypeStruct: 1716 | case YYEncodingTypeUnion: { 1717 | if (propertyMeta->_isKVCCompatible) { 1718 | @try { 1719 | NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name]; 1720 | if (value) [self setValue:value forKey:propertyMeta->_name]; 1721 | } @catch (NSException *exception) {} 1722 | } 1723 | } break; 1724 | 1725 | default: 1726 | break; 1727 | } 1728 | } 1729 | } 1730 | return self; 1731 | } 1732 | 1733 | - (NSUInteger)yy_modelHash { 1734 | if (self == (id)kCFNull) return [self hash]; 1735 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; 1736 | if (modelMeta->_nsType) return [self hash]; 1737 | 1738 | NSUInteger value = 0; 1739 | NSUInteger count = 0; 1740 | for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { 1741 | if (!propertyMeta->_isKVCCompatible) continue; 1742 | value ^= [[self valueForKey:NSStringFromSelector(propertyMeta->_getter)] hash]; 1743 | count++; 1744 | } 1745 | if (count == 0) value = (long)((__bridge void *)self); 1746 | return value; 1747 | } 1748 | 1749 | - (BOOL)yy_modelIsEqual:(id)model { 1750 | if (self == model) return YES; 1751 | if (![model isMemberOfClass:self.class]) return NO; 1752 | _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; 1753 | if (modelMeta->_nsType) return [self isEqual:model]; 1754 | if ([self hash] != [model hash]) return NO; 1755 | 1756 | for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { 1757 | if (!propertyMeta->_isKVCCompatible) continue; 1758 | id this = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; 1759 | id that = [model valueForKey:NSStringFromSelector(propertyMeta->_getter)]; 1760 | if (this == that) continue; 1761 | if (this == nil || that == nil) return NO; 1762 | if (![this isEqual:that]) return NO; 1763 | } 1764 | return YES; 1765 | } 1766 | 1767 | - (NSString *)yy_modelDescription { 1768 | return ModelDescription(self); 1769 | } 1770 | 1771 | @end 1772 | 1773 | 1774 | 1775 | @implementation NSArray (YYModel) 1776 | 1777 | + (NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json { 1778 | if (!json) return nil; 1779 | NSArray *arr = nil; 1780 | NSData *jsonData = nil; 1781 | if ([json isKindOfClass:[NSArray class]]) { 1782 | arr = json; 1783 | } else if ([json isKindOfClass:[NSString class]]) { 1784 | jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; 1785 | } else if ([json isKindOfClass:[NSData class]]) { 1786 | jsonData = json; 1787 | } 1788 | if (jsonData) { 1789 | arr = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; 1790 | if (![arr isKindOfClass:[NSArray class]]) arr = nil; 1791 | } 1792 | return [self yy_modelArrayWithClass:cls array:arr]; 1793 | } 1794 | 1795 | + (NSArray *)yy_modelArrayWithClass:(Class)cls array:(NSArray *)arr { 1796 | if (!cls || !arr) return nil; 1797 | NSMutableArray *result = [NSMutableArray new]; 1798 | for (NSDictionary *dic in arr) { 1799 | if (![dic isKindOfClass:[NSDictionary class]]) continue; 1800 | NSObject *obj = [cls yy_modelWithDictionary:dic]; 1801 | if (obj) [result addObject:obj]; 1802 | } 1803 | return result; 1804 | } 1805 | 1806 | @end 1807 | 1808 | 1809 | @implementation NSDictionary (YYModel) 1810 | 1811 | + (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json { 1812 | if (!json) return nil; 1813 | NSDictionary *dic = nil; 1814 | NSData *jsonData = nil; 1815 | if ([json isKindOfClass:[NSDictionary class]]) { 1816 | dic = json; 1817 | } else if ([json isKindOfClass:[NSString class]]) { 1818 | jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; 1819 | } else if ([json isKindOfClass:[NSData class]]) { 1820 | jsonData = json; 1821 | } 1822 | if (jsonData) { 1823 | dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; 1824 | if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; 1825 | } 1826 | return [self yy_modelDictionaryWithClass:cls dictionary:dic]; 1827 | } 1828 | 1829 | + (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic { 1830 | if (!cls || !dic) return nil; 1831 | NSMutableDictionary *result = [NSMutableDictionary new]; 1832 | for (NSString *key in dic.allKeys) { 1833 | if (![key isKindOfClass:[NSString class]]) continue; 1834 | NSObject *obj = [cls yy_modelWithDictionary:dic[key]]; 1835 | if (obj) result[key] = obj; 1836 | } 1837 | return result; 1838 | } 1839 | 1840 | @end 1841 | -------------------------------------------------------------------------------- /YYModel-Demo/YYModel/YYClassInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYClassInfo.h 3 | // YYModel 4 | // 5 | // Created by ibireme on 15/5/9. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /** 18 | Type encoding's type. 19 | */ 20 | typedef NS_OPTIONS(NSUInteger, YYEncodingType) { 21 | YYEncodingTypeMask = 0xFF, ///< mask of type value 22 | YYEncodingTypeUnknown = 0, ///< unknown 23 | YYEncodingTypeVoid = 1, ///< void 24 | YYEncodingTypeBool = 2, ///< bool 25 | YYEncodingTypeInt8 = 3, ///< char / BOOL 26 | YYEncodingTypeUInt8 = 4, ///< unsigned char 27 | YYEncodingTypeInt16 = 5, ///< short 28 | YYEncodingTypeUInt16 = 6, ///< unsigned short 29 | YYEncodingTypeInt32 = 7, ///< int 30 | YYEncodingTypeUInt32 = 8, ///< unsigned int 31 | YYEncodingTypeInt64 = 9, ///< long long 32 | YYEncodingTypeUInt64 = 10, ///< unsigned long long 33 | YYEncodingTypeFloat = 11, ///< float 34 | YYEncodingTypeDouble = 12, ///< double 35 | YYEncodingTypeLongDouble = 13, ///< long double 36 | YYEncodingTypeObject = 14, ///< id 37 | YYEncodingTypeClass = 15, ///< Class 38 | YYEncodingTypeSEL = 16, ///< SEL 39 | YYEncodingTypeBlock = 17, ///< block 40 | YYEncodingTypePointer = 18, ///< void* 41 | YYEncodingTypeStruct = 19, ///< struct 42 | YYEncodingTypeUnion = 20, ///< union 43 | YYEncodingTypeCString = 21, ///< char* 44 | YYEncodingTypeCArray = 22, ///< char[10] (for example) 45 | 46 | YYEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier 47 | YYEncodingTypeQualifierConst = 1 << 8, ///< const 48 | YYEncodingTypeQualifierIn = 1 << 9, ///< in 49 | YYEncodingTypeQualifierInout = 1 << 10, ///< inout 50 | YYEncodingTypeQualifierOut = 1 << 11, ///< out 51 | YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy 52 | YYEncodingTypeQualifierByref = 1 << 13, ///< byref 53 | YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway 54 | 55 | YYEncodingTypePropertyMask = 0xFF0000, ///< mask of property 56 | YYEncodingTypePropertyReadonly = 1 << 16, ///< readonly 57 | YYEncodingTypePropertyCopy = 1 << 17, ///< copy 58 | YYEncodingTypePropertyRetain = 1 << 18, ///< retain 59 | YYEncodingTypePropertyNonatomic = 1 << 19, ///< nonatomic 60 | YYEncodingTypePropertyWeak = 1 << 20, ///< weak 61 | YYEncodingTypePropertyCustomGetter = 1 << 21, ///< getter= 62 | YYEncodingTypePropertyCustomSetter = 1 << 22, ///< setter= 63 | YYEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic 64 | }; 65 | 66 | /** 67 | Get the type from a Type-Encoding string. 68 | 69 | @discussion See also: 70 | https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html 71 | https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html 72 | 73 | @param typeEncoding A Type-Encoding string. 74 | @return The encoding type. 75 | */ 76 | YYEncodingType YYEncodingGetType(const char *typeEncoding); 77 | 78 | 79 | /** 80 | Instance variable information. 81 | */ 82 | @interface YYClassIvarInfo : NSObject 83 | @property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct 84 | @property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name 85 | @property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset 86 | @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding 87 | @property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type 88 | 89 | /** 90 | Creates and returns an ivar info object. 91 | 92 | @param ivar ivar opaque struct 93 | @return A new object, or nil if an error occurs. 94 | */ 95 | - (instancetype)initWithIvar:(Ivar)ivar; 96 | @end 97 | 98 | 99 | /** 100 | Method information. 101 | */ 102 | @interface YYClassMethodInfo : NSObject 103 | @property (nonatomic, assign, readonly) Method method; ///< method opaque struct 104 | @property (nonatomic, strong, readonly) NSString *name; ///< method name 105 | @property (nonatomic, assign, readonly) SEL sel; ///< method's selector 106 | @property (nonatomic, assign, readonly) IMP imp; ///< method's implementation 107 | @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types 108 | @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type 109 | @property (nullable, nonatomic, strong, readonly) NSArray *argumentTypeEncodings; ///< array of arguments' type 110 | 111 | /** 112 | Creates and returns a method info object. 113 | 114 | @param method method opaque struct 115 | @return A new object, or nil if an error occurs. 116 | */ 117 | - (instancetype)initWithMethod:(Method)method; 118 | @end 119 | 120 | 121 | /** 122 | Property information. 123 | */ 124 | @interface YYClassPropertyInfo : NSObject 125 | @property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct 126 | @property (nonatomic, strong, readonly) NSString *name; ///< property's name 127 | @property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type 128 | @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value 129 | @property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name 130 | @property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil 131 | @property (nullable, nonatomic, strong, readonly) NSArray *protocols; ///< may nil 132 | @property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) 133 | @property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) 134 | 135 | /** 136 | Creates and returns a property info object. 137 | 138 | @param property property opaque struct 139 | @return A new object, or nil if an error occurs. 140 | */ 141 | - (instancetype)initWithProperty:(objc_property_t)property; 142 | @end 143 | 144 | 145 | /** 146 | Class information for a class. 147 | */ 148 | @interface YYClassInfo : NSObject 149 | @property (nonatomic, assign, readonly) Class cls; ///< class object 150 | @property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object 151 | @property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object 152 | @property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class 153 | @property (nonatomic, strong, readonly) NSString *name; ///< class name 154 | @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info 155 | @property (nullable, nonatomic, strong, readonly) NSDictionary *ivarInfos; ///< ivars 156 | @property (nullable, nonatomic, strong, readonly) NSDictionary *methodInfos; ///< methods 157 | @property (nullable, nonatomic, strong, readonly) NSDictionary *propertyInfos; ///< properties 158 | 159 | /** 160 | If the class is changed (for example: you add a method to this class with 161 | 'class_addMethod()'), you should call this method to refresh the class info cache. 162 | 163 | After called this method, `needUpdate` will returns `YES`, and you should call 164 | 'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info. 165 | */ 166 | - (void)setNeedUpdate; 167 | 168 | /** 169 | If this method returns `YES`, you should stop using this instance and call 170 | `classInfoWithClass` or `classInfoWithClassName` to get the updated class info. 171 | 172 | @return Whether this class info need update. 173 | */ 174 | - (BOOL)needUpdate; 175 | 176 | /** 177 | Get the class info of a specified Class. 178 | 179 | @discussion This method will cache the class info and super-class info 180 | at the first access to the Class. This method is thread-safe. 181 | 182 | @param cls A class. 183 | @return A class info, or nil if an error occurs. 184 | */ 185 | + (nullable instancetype)classInfoWithClass:(Class)cls; 186 | 187 | /** 188 | Get the class info of a specified Class. 189 | 190 | @discussion This method will cache the class info and super-class info 191 | at the first access to the Class. This method is thread-safe. 192 | 193 | @param className A class name. 194 | @return A class info, or nil if an error occurs. 195 | */ 196 | + (nullable instancetype)classInfoWithClassName:(NSString *)className; 197 | 198 | @end 199 | 200 | NS_ASSUME_NONNULL_END 201 | -------------------------------------------------------------------------------- /YYModel-Demo/YYModel/YYClassInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYClassInfo.m 3 | // YYModel 4 | // 5 | // Created by ibireme on 15/5/9. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYClassInfo.h" 13 | #import 14 | 15 | YYEncodingType YYEncodingGetType(const char *typeEncoding) { 16 | char *type = (char *)typeEncoding; 17 | if (!type) return YYEncodingTypeUnknown; 18 | size_t len = strlen(type); 19 | if (len == 0) return YYEncodingTypeUnknown; 20 | 21 | YYEncodingType qualifier = 0; 22 | bool prefix = true; 23 | while (prefix) { 24 | switch (*type) { 25 | case 'r': { 26 | qualifier |= YYEncodingTypeQualifierConst; 27 | type++; 28 | } break; 29 | case 'n': { 30 | qualifier |= YYEncodingTypeQualifierIn; 31 | type++; 32 | } break; 33 | case 'N': { 34 | qualifier |= YYEncodingTypeQualifierInout; 35 | type++; 36 | } break; 37 | case 'o': { 38 | qualifier |= YYEncodingTypeQualifierOut; 39 | type++; 40 | } break; 41 | case 'O': { 42 | qualifier |= YYEncodingTypeQualifierBycopy; 43 | type++; 44 | } break; 45 | case 'R': { 46 | qualifier |= YYEncodingTypeQualifierByref; 47 | type++; 48 | } break; 49 | case 'V': { 50 | qualifier |= YYEncodingTypeQualifierOneway; 51 | type++; 52 | } break; 53 | default: { prefix = false; } break; 54 | } 55 | } 56 | 57 | len = strlen(type); 58 | if (len == 0) return YYEncodingTypeUnknown | qualifier; 59 | 60 | switch (*type) { 61 | case 'v': return YYEncodingTypeVoid | qualifier; 62 | case 'B': return YYEncodingTypeBool | qualifier; 63 | case 'c': return YYEncodingTypeInt8 | qualifier; 64 | case 'C': return YYEncodingTypeUInt8 | qualifier; 65 | case 's': return YYEncodingTypeInt16 | qualifier; 66 | case 'S': return YYEncodingTypeUInt16 | qualifier; 67 | case 'i': return YYEncodingTypeInt32 | qualifier; 68 | case 'I': return YYEncodingTypeUInt32 | qualifier; 69 | case 'l': return YYEncodingTypeInt32 | qualifier; 70 | case 'L': return YYEncodingTypeUInt32 | qualifier; 71 | case 'q': return YYEncodingTypeInt64 | qualifier; 72 | case 'Q': return YYEncodingTypeUInt64 | qualifier; 73 | case 'f': return YYEncodingTypeFloat | qualifier; 74 | case 'd': return YYEncodingTypeDouble | qualifier; 75 | case 'D': return YYEncodingTypeLongDouble | qualifier; 76 | case '#': return YYEncodingTypeClass | qualifier; 77 | case ':': return YYEncodingTypeSEL | qualifier; 78 | case '*': return YYEncodingTypeCString | qualifier; 79 | case '^': return YYEncodingTypePointer | qualifier; 80 | case '[': return YYEncodingTypeCArray | qualifier; 81 | case '(': return YYEncodingTypeUnion | qualifier; 82 | case '{': return YYEncodingTypeStruct | qualifier; 83 | case '@': { 84 | if (len == 2 && *(type + 1) == '?') 85 | return YYEncodingTypeBlock | qualifier; 86 | else 87 | return YYEncodingTypeObject | qualifier; 88 | } 89 | default: return YYEncodingTypeUnknown | qualifier; 90 | } 91 | } 92 | 93 | @implementation YYClassIvarInfo 94 | 95 | - (instancetype)initWithIvar:(Ivar)ivar { 96 | if (!ivar) return nil; 97 | self = [super init]; 98 | _ivar = ivar; 99 | const char *name = ivar_getName(ivar); 100 | if (name) { 101 | _name = [NSString stringWithUTF8String:name]; 102 | } 103 | _offset = ivar_getOffset(ivar); 104 | const char *typeEncoding = ivar_getTypeEncoding(ivar); 105 | if (typeEncoding) { 106 | _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; 107 | _type = YYEncodingGetType(typeEncoding); 108 | } 109 | return self; 110 | } 111 | 112 | @end 113 | 114 | @implementation YYClassMethodInfo 115 | 116 | - (instancetype)initWithMethod:(Method)method { 117 | if (!method) return nil; 118 | self = [super init]; 119 | _method = method; 120 | _sel = method_getName(method); 121 | _imp = method_getImplementation(method); 122 | const char *name = sel_getName(_sel); 123 | if (name) { 124 | _name = [NSString stringWithUTF8String:name]; 125 | } 126 | const char *typeEncoding = method_getTypeEncoding(method); 127 | if (typeEncoding) { 128 | _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; 129 | } 130 | char *returnType = method_copyReturnType(method); 131 | if (returnType) { 132 | _returnTypeEncoding = [NSString stringWithUTF8String:returnType]; 133 | free(returnType); 134 | } 135 | unsigned int argumentCount = method_getNumberOfArguments(method); 136 | if (argumentCount > 0) { 137 | NSMutableArray *argumentTypes = [NSMutableArray new]; 138 | for (unsigned int i = 0; i < argumentCount; i++) { 139 | char *argumentType = method_copyArgumentType(method, i); 140 | NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil; 141 | [argumentTypes addObject:type ? type : @""]; 142 | if (argumentType) free(argumentType); 143 | } 144 | _argumentTypeEncodings = argumentTypes; 145 | } 146 | return self; 147 | } 148 | 149 | @end 150 | 151 | @implementation YYClassPropertyInfo 152 | 153 | - (instancetype)initWithProperty:(objc_property_t)property { 154 | if (!property) return nil; 155 | self = [super init]; 156 | _property = property; 157 | const char *name = property_getName(property); 158 | if (name) { 159 | _name = [NSString stringWithUTF8String:name]; 160 | } 161 | 162 | YYEncodingType type = 0; 163 | unsigned int attrCount; 164 | objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); 165 | for (unsigned int i = 0; i < attrCount; i++) { 166 | switch (attrs[i].name[0]) { 167 | case 'T': { // Type encoding 168 | if (attrs[i].value) { 169 | _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; 170 | type = YYEncodingGetType(attrs[i].value); 171 | 172 | if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) { 173 | NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; 174 | if (![scanner scanString:@"@\"" intoString:NULL]) continue; 175 | 176 | NSString *clsName = nil; 177 | if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) { 178 | if (clsName.length) _cls = objc_getClass(clsName.UTF8String); 179 | } 180 | 181 | NSMutableArray *protocols = nil; 182 | while ([scanner scanString:@"<" intoString:NULL]) { 183 | NSString* protocol = nil; 184 | if ([scanner scanUpToString:@">" intoString: &protocol]) { 185 | if (protocol.length) { 186 | if (!protocols) protocols = [NSMutableArray new]; 187 | [protocols addObject:protocol]; 188 | } 189 | } 190 | [scanner scanString:@">" intoString:NULL]; 191 | } 192 | _protocols = protocols; 193 | } 194 | } 195 | } break; 196 | case 'V': { // Instance variable 197 | if (attrs[i].value) { 198 | _ivarName = [NSString stringWithUTF8String:attrs[i].value]; 199 | } 200 | } break; 201 | case 'R': { 202 | type |= YYEncodingTypePropertyReadonly; 203 | } break; 204 | case 'C': { 205 | type |= YYEncodingTypePropertyCopy; 206 | } break; 207 | case '&': { 208 | type |= YYEncodingTypePropertyRetain; 209 | } break; 210 | case 'N': { 211 | type |= YYEncodingTypePropertyNonatomic; 212 | } break; 213 | case 'D': { 214 | type |= YYEncodingTypePropertyDynamic; 215 | } break; 216 | case 'W': { 217 | type |= YYEncodingTypePropertyWeak; 218 | } break; 219 | case 'G': { 220 | type |= YYEncodingTypePropertyCustomGetter; 221 | if (attrs[i].value) { 222 | _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); 223 | } 224 | } break; 225 | case 'S': { 226 | type |= YYEncodingTypePropertyCustomSetter; 227 | if (attrs[i].value) { 228 | _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); 229 | } 230 | } // break; commented for code coverage in next line 231 | default: break; 232 | } 233 | } 234 | if (attrs) { 235 | free(attrs); 236 | attrs = NULL; 237 | } 238 | 239 | _type = type; 240 | if (_name.length) { 241 | if (!_getter) { 242 | _getter = NSSelectorFromString(_name); 243 | } 244 | if (!_setter) { 245 | _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); 246 | } 247 | } 248 | return self; 249 | } 250 | 251 | @end 252 | 253 | @implementation YYClassInfo { 254 | BOOL _needUpdate; 255 | } 256 | 257 | - (instancetype)initWithClass:(Class)cls { 258 | if (!cls) return nil; 259 | self = [super init]; 260 | _cls = cls; 261 | _superCls = class_getSuperclass(cls); 262 | _isMeta = class_isMetaClass(cls); 263 | if (!_isMeta) { 264 | _metaCls = objc_getMetaClass(class_getName(cls)); 265 | } 266 | _name = NSStringFromClass(cls); 267 | [self _update]; 268 | 269 | _superClassInfo = [self.class classInfoWithClass:_superCls]; 270 | return self; 271 | } 272 | 273 | - (void)_update { 274 | _ivarInfos = nil; 275 | _methodInfos = nil; 276 | _propertyInfos = nil; 277 | 278 | Class cls = self.cls; 279 | unsigned int methodCount = 0; 280 | Method *methods = class_copyMethodList(cls, &methodCount); 281 | if (methods) { 282 | NSMutableDictionary *methodInfos = [NSMutableDictionary new]; 283 | _methodInfos = methodInfos; 284 | for (unsigned int i = 0; i < methodCount; i++) { 285 | YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; 286 | if (info.name) methodInfos[info.name] = info; 287 | } 288 | free(methods); 289 | } 290 | unsigned int propertyCount = 0; 291 | objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); 292 | if (properties) { 293 | NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; 294 | _propertyInfos = propertyInfos; 295 | for (unsigned int i = 0; i < propertyCount; i++) { 296 | YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; 297 | if (info.name) propertyInfos[info.name] = info; 298 | } 299 | free(properties); 300 | } 301 | 302 | unsigned int ivarCount = 0; 303 | Ivar *ivars = class_copyIvarList(cls, &ivarCount); 304 | if (ivars) { 305 | NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; 306 | _ivarInfos = ivarInfos; 307 | for (unsigned int i = 0; i < ivarCount; i++) { 308 | YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; 309 | if (info.name) ivarInfos[info.name] = info; 310 | } 311 | free(ivars); 312 | } 313 | 314 | if (!_ivarInfos) _ivarInfos = @{}; 315 | if (!_methodInfos) _methodInfos = @{}; 316 | if (!_propertyInfos) _propertyInfos = @{}; 317 | 318 | _needUpdate = NO; 319 | } 320 | 321 | - (void)setNeedUpdate { 322 | _needUpdate = YES; 323 | } 324 | 325 | - (BOOL)needUpdate { 326 | return _needUpdate; 327 | } 328 | 329 | + (instancetype)classInfoWithClass:(Class)cls { 330 | if (!cls) return nil; 331 | static CFMutableDictionaryRef classCache; 332 | static CFMutableDictionaryRef metaCache; 333 | static dispatch_once_t onceToken; 334 | static dispatch_semaphore_t lock; 335 | dispatch_once(&onceToken, ^{ 336 | classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 337 | metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 338 | lock = dispatch_semaphore_create(1); 339 | }); 340 | dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); 341 | YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); 342 | if (info && info->_needUpdate) { 343 | [info _update]; 344 | } 345 | dispatch_semaphore_signal(lock); 346 | if (!info) { 347 | info = [[YYClassInfo alloc] initWithClass:cls]; 348 | if (info) { 349 | dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); 350 | CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info)); 351 | dispatch_semaphore_signal(lock); 352 | } 353 | } 354 | return info; 355 | } 356 | 357 | + (instancetype)classInfoWithClassName:(NSString *)className { 358 | Class cls = NSClassFromString(className); 359 | return [self classInfoWithClass:cls]; 360 | } 361 | 362 | @end 363 | -------------------------------------------------------------------------------- /YYModel-Demo/YYModel/YYModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYModel.h 3 | // YYModel 4 | // 5 | // Created by ibireme on 15/5/10. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | FOUNDATION_EXPORT double YYModelVersionNumber; 16 | FOUNDATION_EXPORT const unsigned char YYModelVersionString[]; 17 | #import 18 | #import 19 | #else 20 | #import "NSObject+YYModel.h" 21 | #import "YYClassInfo.h" 22 | #endif 23 | -------------------------------------------------------------------------------- /YYModel-Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YYModel-Demo 4 | // 5 | // Created by 郭彬 on 16/6/22. 6 | // Copyright © 2016年 walker. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /YYModel-Demo/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Harry", 3 | "timestamp" : 1445534567 4 | } --------------------------------------------------------------------------------