关于YYModel的简单总结。


尝试阅读YYModel源码,发现有一些细节并不能十分透彻地理解清楚,只能略微窥探到其中主要原理。这里就当做第一遍阅读笔记😂😂😂。

使用

分类

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
@interface NSObject (YYModel)
+ (nullable instancetype)yy_modelWithJSON:(id)json;
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
- (BOOL)yy_modelSetWithJSON:(id)json;
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
- (nullable id)yy_modelToJSONObject;
- (nullable NSData *)yy_modelToJSONData;
- (nullable NSString *)yy_modelToJSONString;

// 自定义
- (nullable id)yy_modelCopy;
- (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder;
- (id)yy_modelInitWithCoder:(NSCoder *)aDecoder;
- (NSUInteger)yy_modelHash;
- (BOOL)yy_modelIsEqual:(id)model;
- (NSString *)yy_modelDescription;
@end


@interface NSArray (YYModel)
// json到模型数组
+ (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;
@end


@interface NSDictionary (YYModel)
// json到字典
+ (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
@end

拓展

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@protocol YYModel <NSObject>
@optional
// 自定义模型属性与json's key的映射
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
+ (NSDictionary *)modelCustomPropertyMapper {
return @{ @"name" : @"n",
@"count" : @"ext.c",
@"desc1" : @"ext.d",
@"desc4" : @".ext",
@"modelID" : @[@"ID", @"Id", @"id", @"ext.id"]};
}


// 黑名单, 若实现该方法, 黑名单之内的key均不作处理
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
+ (NSArray *)modelPropertyBlacklist {
return @[@"name", @"age"];
}

// 白名单, 若实现该方法, 白名单之外的key均不作处理
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
+ (NSArray *)modelPropertyWhitelist {
return @[@"name"];
}

// 1.要在JSON转Model的过程中根据情况创建不同类型的实例
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
if (dictionary[@"localName"]) {
return [YYLocalUser class];
} else if (dictionary[@"remoteName"]) {
return [YYRemoteUser class];
}
return [YYBaseUser class];
}

// 2.该方法发生在字典转模型之前, 最后对字典做一次处理
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
//- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic{
if ([dic[@"sex"] isEqualToString:@"Man"]) {
return nil;
}
return dic;
}

// 3.JSON转为Model后, 进行数据校验
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
NSNumber *timestamp = dic[@"timestamp"];
if (![timestamp isKindOfClass:[NSNumber class]]) return NO;
_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
return YES;
}

// 模型容器属性中的所需要存放的数据类型
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"shadows" : [Shadow class],
@"borders" : Border.class,
@"attachments" : @"Attachment" };
}

// Model转为JSON后, 进行数据校验
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
if (!_createdAt) return NO;
dic[@"timestamp"] = @(_createdAt.timeIntervalSince1970);
return YES;
}
@end

函数

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665

// 函数1: 根据class信息获取其对应的类型
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
if (!cls) return YYEncodingTypeNSUnknown;
if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
return YYEncodingTypeNSUnknown;
}

// 函数2: 判断YYEncodingType是不是一个数字(整形\长整型\浮点型等)
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
switch (type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
case YYEncodingTypeUInt8:
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16:
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32:
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64:
case YYEncodingTypeFloat:
case YYEncodingTypeDouble:
case YYEncodingTypeLongDouble: return YES;
default: return NO;
}
}

// 函数3: 根据一个id类型的对象创建一个NSNumber类型的对象
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) {
static NSCharacterSet *dot;
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
dic = @{@"TRUE" : @(YES),
@"True" : @(YES),
@"true" : @(YES),
@"FALSE" : @(NO),
@"False" : @(NO),
@"false" : @(NO),
@"YES" : @(YES),
@"Yes" : @(YES),
@"yes" : @(YES),
@"NO" : @(NO),
@"No" : @(NO),
@"no" : @(NO),
@"NIL" : (id)kCFNull,
@"Nil" : (id)kCFNull,
@"nil" : (id)kCFNull,
@"NULL" : (id)kCFNull,
@"Null" : (id)kCFNull,
@"null" : (id)kCFNull,
@"(NULL)" : (id)kCFNull,
@"(Null)" : (id)kCFNull,
@"(null)" : (id)kCFNull,
@"<NULL>" : (id)kCFNull,
@"<Null>" : (id)kCFNull,
@"<null>" : (id)kCFNull};
});

// kCFNull单例
if (!value || value == (id)kCFNull) return nil;
// NSNumber直接返回
if ([value isKindOfClass:[NSNumber class]]) return value;
// NSString, 取出dic中对应的值
if ([value isKindOfClass:[NSString class]]) {
NSNumber *num = dic[value];
if (num) {
if (num == (id)kCFNull) return nil;
return num;
}
// 这个字符串中含有 ".", 例如 @"12.344"
if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
double num = atof(cstring);
// isfinite()测试某个浮点数是不是有限的数
// isinf()测试某个浮点数是否是无限大
// isnan()测试某个浮点数是否是 非数字
if (isnan(num) || isinf(num)) return nil; // num是否是无限大或者是否是非数字
return @(num); // return @(12.344);
} else {
// 字符串中没有".", 例如 @"1323"
const char *cstring = ((NSString *)value).UTF8String;// 转化为C字符串"1323"
if (!cstring) return nil;
// atoi函数:将字符串转化为int类型变量. atol函数:将字符串转化为long类型变量.
// atoll函数:将字符串转化为long long类型变量.atof函数:将字符串转化为double类型变量
return @(atoll(cstring)); // 转换为long long类型变量
}
}
return nil;
}

// 函数4: 将字符串转化为日期NSDate
// 根据string的length判断需要调用哪一个block, 为了避免效率较低的if-else, 采用block数组的形式, string的length正好对应blocks数组的索引, 即查表法, 效率得到提升.
// YYNSDateParseBlock parser = blocks[string.length];
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34
// 定义一个block数组, 数组是C数组
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
{
/*
2014-01-20 // Google , 10个字符, 对应blocks[10]
*/
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter.dateFormat = @"yyyy-MM-dd";
blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; };
}

{
/*
2014-01-20 12:24:48 // 19个字符, 对应blocks[19]
2014-01-20T12:24:48 // Google, 19个字符, 对应blocks[19]
2014-01-20 12:24:48.000 // 23个字符, 对应blocks[23]
2014-01-20T12:24:48.000 // 23个字符, 对应blocks[23]
*/
NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";

NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";

NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";

NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";

blocks[19] = ^(NSString *string) {
if ([string characterAtIndex:10] == 'T') {// 2014-01-20T12:24:48
return [formatter1 dateFromString:string];
} else {// 2014-01-20 12:24:48
return [formatter2 dateFromString:string];
}
};

blocks[23] = ^(NSString *string) {
if ([string characterAtIndex:10] == 'T') {// 2014-01-20T12:24:48.000
return [formatter3 dateFromString:string];
} else {// 2014-01-20 12:24:48.000
return [formatter4 dateFromString:string];
}
};
}

{
/*
2014-01-20T12:24:48Z // Github, Apple
2014-01-20T12:24:48+0800 // Facebook
2014-01-20T12:24:48+12:00 // Google
2014-01-20T12:24:48.000Z
2014-01-20T12:24:48.000+0800
2014-01-20T12:24:48.000+12:00
*/
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";

NSDateFormatter *formatter2 = [NSDateFormatter new];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";

blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; };
blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; };
blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; };
blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
}

{
/*
Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter
Fri Sep 04 00:12:21.000 +0800 2015
*/
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";

NSDateFormatter *formatter2 = [NSDateFormatter new];
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";

blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; };
blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
}
});
if (!string) return nil;
if (string.length > kParserNum) return nil;
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum
}


// 函数5: 获取NSBlock类
static force_inline Class YYNSBlockClass() {
static Class cls;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void (^block)(void) = ^{};
cls = ((NSObject *)block).class;
while (class_getSuperclass(cls) != [NSObject class]) {
cls = class_getSuperclass(cls);
}
});
return cls; // current is "NSBlock"
}


// 函数6: 获取ISO NSDateFormatter
/**
example:
2010-07-09T16:13:30+12:00
2011-01-11T11:11:11+0000
2011-01-26T19:06:43Z
length: 20/24/25
*/
static force_inline NSDateFormatter *YYISODateFormatter() {
static NSDateFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
});
return formatter;
}


/*
{
"version": "1.1",
"object": {
"data": {
"phone": "12332123"
}
}
}
*/
// 函数7: 根据keypath从字典中获取对应的值(这个值是id类型)
// keyPaths: @[@"object", @"data", @"phone"] 对应的值是 @"12332123"
// keyPaths: @[@"object", @"data"] 对应的值是 @{@"phone": @"12332123"}

static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
id value = nil;
for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
value = dic[keyPaths[i]];
if (i + 1 < max) {
if ([value isKindOfClass:[NSDictionary class]]) {
dic = value;
} else {
return nil;
}
}
}
return value;
}

// 函数8: 根据可变的keypath从字典中获取对应的值(这个值是id类型)
// multiKeys: @[ @[@"object", @"data"], @"phone"] 对应的值是 @{@"phone": @"12332123"}
// multiKeys: @[@"object", @"data"] 对应的值是 @{@"data": @{@"phone": @"12332123"}}
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
id value = nil;
for (NSString *key in multiKeys) {
if ([key isKindOfClass:[NSString class]]) {
value = dic[key];
if (value) break;
} else {
value = YYValueForKeyPath(dic, (NSArray *)key);
if (value) break;
}
}
return value;
}


// 函数9: 从模型中的属性(_YYModelPropertyMeta类型)中获取NSNumber
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,
__unsafe_unretained _YYModelPropertyMeta *meta) {
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool: {
return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));}
//......
}
}


// 函数10: 对模型中的属性(_YYModelPropertyMeta类型)设值. 和函数9类似
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
__unsafe_unretained NSNumber *num,
__unsafe_unretained _YYModelPropertyMeta *meta) {
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool: {
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
} break;
// ......
}
}

// 函数11: 对模型中的属性(_YYModelPropertyMeta类型)设值
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
//......
}


typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;


// 函数12: 对模型(_context.modelMeta and _context.model)设置 key-value键值对
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
__unsafe_unretained id model = (__bridge id)(context->model);
while (propertyMeta) {
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
// 有多个属性映射到同一个 key 则指向下一个模型属性元
propertyMeta = propertyMeta->_next;
};
}

/**
Apply function for model property meta, to set dictionary to model.

@param _propertyMeta should not be nil, _YYModelPropertyMeta.
@param _context _context.model and _context.dictionary should not be nil.
*/
// 函数13:
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
if (!propertyMeta->_setter) return;
id value = nil;

if (propertyMeta->_mappedToKeyArray) {
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}

if (value) {
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}

/**
Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull),
or nil if an error occurs.

@param model Model, can be nil.
@return JSON object, nil if an error occurs.
*/
// 函数14: 模型转 json
static id ModelToJSONObjectRecursive(NSObject *model) {
if (!model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;
if ([model isKindOfClass:[NSDictionary class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableDictionary *newDic = [NSMutableDictionary new];
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
if (!stringKey) return;
id jsonObj = ModelToJSONObjectRecursive(obj);
if (!jsonObj) jsonObj = (id)kCFNull;
newDic[stringKey] = jsonObj;
}];
return newDic;
}
if ([model isKindOfClass:[NSSet class]]) {
NSArray *array = ((NSSet *)model).allObjects;
if ([NSJSONSerialization isValidJSONObject:array]) return array;
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in array) {
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
}
if ([model isKindOfClass:[NSArray class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in (NSArray *)model) {
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
}
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
if ([model isKindOfClass:[NSData class]]) return nil;


_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
__unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
if (!propertyMeta->_getter) return;

id value = nil;
if (propertyMeta->_isCNumber) {
value = ModelCreateNumberFromProperty(model, propertyMeta);
} else if (propertyMeta->_nsType) {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
} else {
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
if (!value) return;

if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
if (i + 1 == max) { // end
if (!superDic[key]) superDic[key] = value;
break;
}

subDic = superDic[key];
if (subDic) {
if ([subDic isKindOfClass:[NSDictionary class]]) {
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break;
}
} else {
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
superDic = subDic;
subDic = nil;
}
} else {
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
}];

if (modelMeta->_hasCustomTransformToDictionary) {
// 当 Model 转为 JSON 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
if (!suc) return nil;
}
return result;
}

/// 函数15: Add indent to string (exclude first line)
static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) {
for (NSUInteger i = 0, max = desc.length; i < max; i++) {
unichar c = [desc characterAtIndex:i];
if (c == '\n') {
for (NSUInteger j = 0; j < indent; j++) {
[desc insertString:@" " atIndex:i + 1];
}
i += indent * 4;
max += indent * 4;
}
}
return desc;
}

/// 函数16: 根据model生成一个描述字符串
static NSString *ModelDescription(NSObject *model) {
static const int kDescMaxLength = 100;
if (!model) return @"<nil>";
if (model == (id)kCFNull) return @"<null>";
if (![model isKindOfClass:[NSObject class]]) return [NSString stringWithFormat:@"%@",model];


_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:model.class];
switch (modelMeta->_nsType) {
case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: {
return [NSString stringWithFormat:@"\"%@\"",model];
}

case YYEncodingTypeNSValue:
case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: {
NSString *tmp = model.description;
if (tmp.length > kDescMaxLength) {
tmp = [tmp substringToIndex:kDescMaxLength];
tmp = [tmp stringByAppendingString:@"..."];
}
return tmp;
}

case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber:
case YYEncodingTypeNSDate:
case YYEncodingTypeNSURL: {
return [NSString stringWithFormat:@"%@",model];
}

case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: {
model = ((NSSet *)model).allObjects;
} // no break

case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: {
NSArray *array = (id)model;
NSMutableString *desc = [NSMutableString new];
if (array.count == 0) {
return [desc stringByAppendingString:@"[]"];
} else {
[desc appendFormat:@"[\n"];
for (NSUInteger i = 0, max = array.count; i < max; i++) {
NSObject *obj = array[i];
[desc appendString:@" "];
[desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)];
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
}
[desc appendString:@"]"];
return desc;
}
}
case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: {
NSDictionary *dic = (id)model;
NSMutableString *desc = [NSMutableString new];
if (dic.count == 0) {
return [desc stringByAppendingString:@"{}"];
} else {
NSArray *keys = dic.allKeys;

[desc appendFormat:@"{\n"];
for (NSUInteger i = 0, max = keys.count; i < max; i++) {
NSString *key = keys[i];
NSObject *value = dic[key];
[desc appendString:@" "];
[desc appendFormat:@"%@ = %@",key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, 1)];
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
}
[desc appendString:@"}"];
}
return desc;
}

default: {
NSMutableString *desc = [NSMutableString new];
[desc appendFormat:@"<%@: %p>", model.class, model];
if (modelMeta->_allPropertyMetas.count == 0) return desc;

// sort property names
NSArray *properties = [modelMeta->_allPropertyMetas
sortedArrayUsingComparator:^NSComparisonResult(_YYModelPropertyMeta *p1, _YYModelPropertyMeta *p2) {
return [p1->_name compare:p2->_name];
}];

[desc appendFormat:@" {\n"];
for (NSUInteger i = 0, max = properties.count; i < max; i++) {
_YYModelPropertyMeta *property = properties[i];
NSString *propertyDesc;
if (property->_isCNumber) {
NSNumber *num = ModelCreateNumberFromProperty(model, property);
propertyDesc = num.stringValue;
} else {
switch (property->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = ModelDescription(v);
if (!propertyDesc) propertyDesc = @"<nil>";
} break;
case YYEncodingTypeClass: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = ((NSObject *)v).description;
if (!propertyDesc) propertyDesc = @"<nil>";
} break;
case YYEncodingTypeSEL: {
SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
if (sel) propertyDesc = NSStringFromSelector(sel);
else propertyDesc = @"<NULL>";
} break;
case YYEncodingTypeBlock: {
id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = block ? ((NSObject *)block).description : @"<nil>";
} break;
case YYEncodingTypeCArray: case YYEncodingTypeCString: case YYEncodingTypePointer: {
void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = [NSString stringWithFormat:@"%p",pointer];
} break;
case YYEncodingTypeStruct: case YYEncodingTypeUnion: {
NSValue *value = [model valueForKey:property->_name];
propertyDesc = value ? value.description : @"{unknown}";
} break;
default: propertyDesc = @"<unknown>";
}
}

propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, 1);
[desc appendFormat:@" %@ = %@",property->_name, propertyDesc];
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
}
[desc appendFormat:@"}"];
return desc;
}
}
}

关键点

1.强制内联

1
#define force_inline __inline__ __attribute__((always_inline))

2.使用代码块, 节省许多变量名

1
2
3
4
5
6
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
}
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
}

3.使用block数组

1
2
3
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
// 定义一个block数组
static YYNSDateParseBlock blocks[35] = {0};

4.获取NSBlock类

1
2
3
4
5
void (^block)(void) = ^{};
cls = ((NSObject *)block).class;
while (class_getSuperclass(cls) != [NSObject class]) {
cls = class_getSuperclass(cls);
}

5.函数参数使用__unsafe_unretained
在 ARC 条件下,默认声明的对象是 __strong 类型的,赋值时有可能会产生retain/release调用,如果一个变量在其生命周期内不会被释放,则使用__unsafe_unretained `会节省很大的开销。

访问具有 __weak属性的变量时,实际上会调用 objc_loadWeak()objc_storeWeak() 来完成,这也会带来很大的开销,所以要避免使用 __weak属性。

创建和使用对象时,要尽量避免对象进入autoreleasepool,以避免额外的资源开销。

6.for循环中定义变量, 使用unsigned

1
2
3
for (unsigned i = 0, max = keyPaths.count; i < max; i++) {

}

7.可变量创建静态不变量, 静态常量配合dispatch_once使用

1
2
3
4
5
6
static NSSet *types = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet new];
types = set;
});

8.线程安全的缓存字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static id cacheWithKey(NSString *key) {
if (!key) return nil;
static CFMutableDictionaryRef cache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
id obj = CFDictionaryGetValue(cache, (__bridge const void *)(key));
dispatch_semaphore_signal(lock);
if (!obj) {
obj = [NSObject new]; // Other operation.
if (obj) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(key), (__bridge const void *)(obj));
dispatch_semaphore_signal(lock);
}
}
return obj;
}

9.消息发送调用格式

1
((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)

10.NSNull的单例kCFNull, 推荐使用, 而不是创建[NSNull null]

1
2
3
4
5
6
NSNull *null1 = (id)kCFNull;
NSNull *null2 = [NSNull null];

Class class = Nil;
NSDate *date = nil;
char *p = NULL;

11.YYClassIvarInfo中的 nametypeEncoding 属性都用 strong 修饰。
NSString 这类属性在确定其不会在初始化之后被修改的情况下,使用 strong 做一次单纯的强引用在性能上讲比 copy 要高一些。

12.NSString转C字符串

const char *cstring = ((NSString *)value).UTF8String;

实践

YYModel 最核心的便是通过遍历模型的所有属性,根据字典值来调用属性的 setter 方法。没有使用效率低下的 KVC(效率低下的原因可能是需要对方法进行搜索吧,有空深究)。鉴于此,用了300行代码简单写了一个字典转模型的玩具,权当做是读完 YYModel 的实践吧。试了一下,效果还可以😂。

1
2
3
4
5
6
7
8
9
10
11
12
 @protocol YAModelProtocol <NSObject>
@optional;
+ (NSDictionary <NSString *, NSString *> *)customPropertyKey;
+ (NSDictionary <NSString *, Class>*)classInArray;
@end


@interface NSObject (YAModel)
+ (instancetype)ya_modelWithDictionary:(NSDictionary *)dict;
+ (instancetype)ya_modelWithJSON:(NSData *)data;
+ (NSArray *)ya_modelArrayWithKeyValuesArray:(NSArray <NSDictionary *>*)dictArray;
@end
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// 编码类型
typedef NS_OPTIONS(NSUInteger, YAType) {
YATypeMask = 0xFF,
YATypeUnknown = 0,
YATypeBOOL = 1,
YATypeNSInteger = 2,
YATypeNSUInteger = 3,
YATypeCGFloat = 4,
YATypeObject = 5,
YATypeDate = 6,
YATypeClass = 7,
YATypeSEL = 8,
YATypeArray = 9,
YATypeMutableArray = 10,
YATypeDictionary = 11,
YATypeMutableDictionary = 12,
YATypeSet = 13,
YATypeMutableSet = 14,
YATypeString = 15,
YATypeMutableString = 16,
YATypeData = 17,
YATypeNumber = 18,
YATypeDecimalNumber = 19,
};

@implementation NSObject (YAModel)
static NSDictionary *classArrayDict = nil;
+ (instancetype)ya_modelWithDictionary:(NSDictionary *)dict {
if (!dict || ![dict isKindOfClass:NSDictionary.class]) return nil;
NSDictionary *propertyList = PropertyList(self);
id obj = [self new];
ObjSetWithKeyValueList(obj, propertyList, dict);
classArrayDict = nil; // Clean memory.
return obj;
}

+ (instancetype)ya_modelWithJSON:(NSData *)data {
if (!data || ![data isKindOfClass:NSData.class]) return nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
if ([json isKindOfClass:NSDictionary.class]) {
return [self ya_modelWithDictionary:json];
}
return nil;
}

+ (NSArray *)ya_modelArrayWithKeyValuesArray:(NSArray<NSDictionary *> *)dictArray {
if (!dictArray || ![dictArray isKindOfClass:NSArray.class]) return nil;
NSMutableArray *tmp = [NSMutableArray arrayWithCapacity:dictArray.count];
[dictArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:NSDictionary.class]) {
id model = [[self class] ya_modelWithDictionary:obj];
[tmp addObject:model];
}
}];
return [NSArray arrayWithArray:tmp] ?: nil;
}

// 获取属性列表 key:属性名 value: 属性类型
static NSDictionary *PropertyList(Class cls) {
if (!cls) return nil;
unsigned int count;
objc_property_t *properties = class_copyPropertyList(cls, &count);
NSMutableDictionary *tempDict = [NSMutableDictionary new];
for(int i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
NSString *propertyAttr = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *type = [propertyAttr substringWithRange:NSMakeRange(1, 1)];
if ([type isEqualToString:@"@"]) {
NSArray *components = [propertyAttr componentsSeparatedByString:@"\""];
if (components.count > 2) {
Class propCls = NSClassFromString(components[1]);
if (propCls == NSDate.class) {
type = @"1";
} else if (propCls == NSArray.class) {
type = @"2";
} else if (propCls == NSMutableArray.class) {
type = @"3";
} else if (propCls == NSDictionary.class) {
type = @"4";
} else if (propCls == NSMutableDictionary.class) {
type = @"5";
} else if (propCls == NSSet.class) {
type = @"6";
} else if (propCls == NSMutableSet.class) {
type = @"7";
} else if (propCls == NSString.class) {
type = @"8";
} else if (propCls == NSMutableString.class) {
type = @"9";
} else if (propCls == NSData.class) {
type = @"10";
} else if (propCls == NSNumber.class) {
type = @"11";
} else if (propCls == NSDecimalNumber.class) {
type = @"12";
} else if (propCls == NSObject.class) {
type = @"@";
} else {
type = components[1];
}
}
}
NSNumber *myType = TypeForProperty(type);
[tempDict setObject:myType forKey:propertyName];
if (myType.integerValue == 0) {
[tempDict setObject:type forKey:propertyName];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:tempDict];
}

static NSNumber *TypeForProperty(NSString *type) {
static NSDictionary *_SELDictionary = nil;;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_SELDictionary = @{
@"B": @(YATypeBOOL),
@"q": @(YATypeNSInteger),
@"Q": @(YATypeNSUInteger),
@"d": @(YATypeCGFloat),
@"#": @(YATypeClass),
@":": @(YATypeSEL),
@"@": @(YATypeObject),
@"1": @(YATypeDate),
@"2": @(YATypeArray),
@"3": @(YATypeMutableArray),
@"4": @(YATypeDictionary),
@"5": @(YATypeMutableDictionary),
@"6": @(YATypeSet),
@"7": @(YATypeMutableSet),
@"8": @(YATypeString),
@"9": @(YATypeMutableString),
@"10": @(YATypeData),
@"11": @(YATypeNumber),
@"12": @(YATypeDecimalNumber),
};
});
return _SELDictionary[type] ?: @(YATypeUnknown);
}

static void ObjSetWithArray(Class cls, NSDictionary *propertyDict, NSArray **keyValueArray) {
NSMutableArray *tmpArray = [NSMutableArray array];
[*keyValueArray enumerateObjectsUsingBlock:^(id keyValue, NSUInteger idx, BOOL * _Nonnull stop) {
if ([keyValue isKindOfClass:NSDictionary.class]) {
id obj = [cls new];
ObjSetWithKeyValueList(obj, PropertyList([obj class]), keyValue);
[tmpArray addObject:obj];
}
}];
*keyValueArray = [NSArray arrayWithArray:tmpArray];
}

static void ObjSetWithKeyValueList(id obj, NSDictionary *propertyDict, NSDictionary *dict) {
Class cls = [obj class];
NSDictionary *customPropertyKeyDict = nil;
if ([cls respondsToSelector:@selector(customPropertyKey)]) {
customPropertyKeyDict = [cls customPropertyKey];
}

if ([cls respondsToSelector:@selector(classInArray)] && !classArrayDict) {
classArrayDict = [cls classInArray];
}

[propertyDict.allKeys enumerateObjectsUsingBlock:^(NSString *name, NSUInteger idx, BOOL * _Nonnull stop) {
SEL setter = SetterSelectorFromString(name);
id value = nil;
if (customPropertyKeyDict[name]) {
value = customPropertyKeyDict[name];
} else {
value = dict[name];
}
id propType = propertyDict[name];
YAType type = [propType integerValue];
if (value) {
switch (type & YATypeMask) {
case YATypeBOOL: {
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)obj, setter, [value boolValue]);
} break;
case YATypeNSInteger: {
((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)obj, setter, (int64_t)[value longLongValue]);
} break;
case YATypeNSUInteger: {
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)obj, setter, (uint64_t)[value unsignedLongLongValue]);
} break;
case YATypeCGFloat: {
long double d = [value doubleValue];
if (isnan(d) || isinf(d)) d = 0;
((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)obj, setter, (long double)d);
} break;
case YATypeDecimalNumber: {
if ([value isKindOfClass:NSNumber.class]) {
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]];
((void (*)(id, SEL, NSDecimalNumber *))(void *) objc_msgSend)((id)obj, setter, decNum);
}
} break;
case YATypeClass: {
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)obj, setter, NSClassFromString(value));
} break;
case YATypeSEL:{
((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)obj, setter, NSSelectorFromString(value));
} break;
case YATypeDate:{
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, DateFromString(value));
} break;
case YATypeArray: {
Class cls = classArrayDict[name];
if (cls) {
ObjSetWithArray(cls, PropertyList(cls), &value);
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
case YATypeMutableArray: {
NSString *clsStr = classArrayDict[name];
if (clsStr) {
Class cls = NSClassFromString(clsStr);
ObjSetWithArray(cls, PropertyList(cls), &value);
}
value = [NSMutableArray arrayWithArray:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
case YATypeSet: {
value = [NSSet setWithArray:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
case YATypeMutableSet: {
value = [NSMutableSet setWithArray:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
case YATypeMutableString:
case YATypeMutableDictionary: {
value = [value mutableCopy];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
case YATypeString:
case YATypeDictionary:
case YATypeNumber:
case YATypeUnknown:
case YATypeObject: {
if (type == YATypeUnknown && [propType isKindOfClass:NSString.class]) { // 嵌套模型
Class cls = NSClassFromString(propType);
if (cls && [value isKindOfClass:NSDictionary.class]) {
id obj = [cls new];
ObjSetWithKeyValueList(obj, PropertyList(cls), value);
value = obj;
}
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)obj, setter, value);
} break;
default: break;
}
}
}];
}

// name ==> setName:
static SEL SetterSelectorFromString(NSString *str) {
if (!str || str.length <= 0) return nil;
NSString *result = [NSString stringWithFormat:@"set%@%@:", [str substringToIndex:1].uppercaseString, [str substringFromIndex:1]];
return NSSelectorFromString(result);
}

// date string ==> data // @"2016-7-16 09:33:22"
static NSDate *DateFromString(NSString *string) {
typedef NSDate* (^DateParseBlock)(NSString *string);
static DateParseBlock blocks[20] = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// @"2016-07-16 09:33:22" // 19个字符, 对应blocks[19]
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
blocks[19] = ^(NSString *string) {
return [formatter dateFromString:string];
};
});
if (!string || string.length > 19) return nil;
DateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
}
@end

具体源码放在了我的代码工具库里,YAKit:https://github.com/ChenYalun/YAKit/tree/master/Util/Model

参考资料
https://juejin.im/post/5a097435f265da431769a49c
https://juejin.im/post/5a1296e36fb9a044fb075d5e
https://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/
https://blog.csdn.net/game3108/article/details/52416868