The previous introduction of YYModel how to dictionary model,

YYModel source code analysis: dictionary to model

An overview of the

This article continues to explore modelMeta, a description file that stores the attributes of the model and the corresponding relationship between the keys of the dictionary

Structure:

Four models are mainly used:

  • @interface YYClassPropertyInfo : NSObject

Attributes of the corresponding model

  • @interface YYClassInfo : NSObject

Classes corresponding to the model

Contains attribute dictionary, NSDictionary

  • @interface _YYModelPropertyMeta : NSObject

Properties of the corresponding model, and its processing

Contains a member variable YYClassPropertyInfo *_info,

The property YYClassPropertyInfo obtained by the model and its processing are encapsulated together

  • @interface _YYModelMeta : NSObject

Corresponding model classes, and processing

Contains a member variable YYClassInfo *_classInfo,

The class information YYClassInfo obtained by the model and its processing are encapsulated together

Model, why do you design it like that

Here are two examples

Example one,+ (NSDictionary *)modelCustomPropertyMapper

The model is as follows:


@interface Author : NSObject
@property NSString *name;
@property NSString *birthday;
@end

@implementation Author
@end
    
@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *reporter;

@end

@implementation Book

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"pages" : @"p",
             @"reporter" : @[@"author",@"writer"]};
}

@end

Copy the code

Code implementation

_YYModelMeta: an implementation of NSObject

First tag
  • for[ @"pages" : @"p" ].

A layer of mapping is maintained through the following member variables

NSDictionary *_mapper;
Copy the code
  • for[ @"reporter" : @[@"author",@"writer"].

Records are held through the following member variables

NSArray *_multiKeysPropertyMetas;
Copy the code

The specific implementation

- (instancetype)initWithClass:(Class)cls { // ... NSMutableDictionary *mapper = [NSMutableDictionary new]; / /... if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; // AllPropertyMetas delete @"pages" corresponding // allPropertyMetas delete @"reporter" corresponding [allPropertyMetas RemoveObjectForKey: propertyName]; the if ([mappedToKey isKindOfClass: [nsstrings class]]) {/ / deal with this, / / / @ "pages" : @"p" ] if (mappedToKey.length == 0) return; propertyMeta->_mappedToKey = mappedToKey; NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."]; for (NSString *onePath in keyPath) { if (onePath.length == 0) { NSMutableArray *tmp =  keyPath.mutableCopy; [tmp removeObject:@""]; keyPath = tmp; break; } } if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } propertyMeta->_next = mapper[mappedToKey] ?: Nil; // modify // go here, AllPropertyMetas @"pages" for propertyMeta // becomes allPropertyMetas @"p" for propertyMeta mapper[mappedToKey] = propertyMeta; // if ([mappedToKey isKindOfClass:[NSString class]]) } else if ([mappedToKey isKindOfClass:[NSArray Class]]) {// [@"reporter" : @[@"author",@"writer"]] NSMutableArray *mappedToKeyArray = [NSMutableArray new]; for (NSString *oneKey in ((NSArray *)mappedToKey)) { if (![oneKey isKindOfClass:[NSString class]]) continue; if (oneKey.length == 0) continue; NSArray *keyPath = [oneKey componentsSeparatedByString:@"."]; if (keyPath.count > 1) { [mappedToKeyArray addObject:keyPath]; } Else {[mappedToKeyArray addObject:oneKey];} if (! PropertyMeta ->_mappedToKey) {// Change propertyMeta->_mappedToKey = only once oneKey; propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : Nil;}} if (! PropertyMeta ->_mappedToKey) return; @[@"author",@"writer"] propertyMeta->_mappedToKeyArray = mappedToKeyArray addObject:propertyMeta]; propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; // if ([mappedToKey isKindOfClass:[NSArray class]]) } }]; / / enumerateKeysAndObjectsUsingBlock} / / if ([CLS respondsToSelector: @ the selector (modelCustomPropertyMapper)]) / / abnormal situation:  [ @"pages" : [allPropertyMetas] [allPropertyMetas] [allPropertyMetas] [allPropertyMetas] [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { propertyMeta->_mappedToKey = name; propertyMeta->_next = mapper[name] ?: nil; mapper[name] = propertyMeta; }]; / /... if (mapper.count) _mapper = mapper; // handle this, // [@"reporter" : @[@"author",@"writer"]] if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; / /... }Copy the code
To deal with

In the dictionary transformation model,

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { // ... if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { // [ @"pages" : @ "p"], the following CFDictionaryApplyFunction (CFDictionaryRef dic, ModelSetWithDictionaryFunction, & context). / /... if (modelMeta->_multiKeysPropertyMetas) { // [@"reporter" : @ [@ "author," @ "writer"]]. CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); }} / /... }Copy the code
  • for[ @"pages" : @"p" ]
/ / _key is "p" static void ModelSetWithDictionaryFunction (const void * _key, const void * _value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); // propertyMeta->_name is "pages" // Through the maintenance of a layer of mapping _mapper, __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); While (propertyMeta) {if (propertyMeta->_setter) {// assign, ModelSetValueForProperty(model, (__bridge __unsafe_unretained ID)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; }Copy the code
  • for[ @"reporter" : @[@"author",@"writer"].
The static void ModelSetWithPropertyMetaArrayFunction (const void * _propertyMeta, void * _context) {/ / to get the background information, 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); } / /... __unsafe_unretained id model = (__bridge id)(context->model); // Assignment if (value) {__unsafe_unretained id model = (__bridge id)(context->model); ModelSetValueForProperty(model, value, propertyMeta); }}Copy the code

Multi-key value, get the value, and we’re done

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; } / /... } return value; }Copy the code

Example 2,+ (NSDictionary *)modelContainerPropertyGenericClass

The model is as follows:


@interface Character : NSObject
@property NSString *name;
@property NSString *birthday;
@end

@implementation Character

@end
    
@interface BookTwo : NSObject
@property NSString *name;
@property NSUInteger pages;
@property NSArray *character;

@end

@implementation BookTwo

+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"character" : [Character class] };
}

@end
Copy the code

Code implementation

First tag
  • for
@{@"character" : [Character class] }
Copy the code

This time, _YYModelPropertyMeta: NSObject

The member variable below it records the container class information

 Class _genericCls;    
Copy the code
  • The specific implementation

As above, back to the initialization method of _YYModelMeta,

NSDictionary *genericMapper = nil; if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) { genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass]; NSMutableDictionary * TMP = [NSMutableDictionary new]; NSMutableDictionary * TMP = [NSMutableDictionary new]; [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {if (![key isKindOfClass:[NSString class]]) return; Class meta = object_getClass(obj); if (! Meta) return; if (class_isMetaClass(meta)) {TMP [key] = obj; }]; genericMapper = tmp; } // Initialize all property information _YYModelPropertyMeta NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; While (curClassInfo && curclassinfo.supercls! = nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy) for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {// Filter out NSObject with custom attribute names if (! propertyInfo.name) continue; _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (! meta || ! meta->_name) continue; if (! meta->_getter || ! meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; } / / recursive, enters a layer curClassInfo = curClassInfo. SuperClassInfo; }Copy the code

Go to the initialization method of _YYModelPropertyMeta,

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic { // ... _YYModelPropertyMeta *meta = [self new]; / /... Meta ->_genericCls = generic; / /... }Copy the code
To deal with

In the dictionary transformation model,

Once again into a familiar formula,

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, Void *_context) {// get the information 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); / /... // The assignment method ModelSetValueForProperty(model, (__bridge __unsafe_unretained ID)_value, propertyMeta); / /... }Copy the code

Assignment methods handle data parsing of container classes

static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta) { // ... switch (meta->_nsType) { // ... case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: {if (meta->_genericCls) {// Enter the container class NSArray *valueArr = nil; if ([value isKindOfClass:[NSArray class]]) valueArr = value; else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects; if (valueArr) { NSMutableArray *objectArr = [NSMutableArray new]; For (id one in valueArr) {if ([one isKindOfClass:meta->_genericCls]) { Add [objectArr addObject:one] directly; } else if ([one isKindOfClass:[NSDictionary class]]) {CLS = meta->_genericCls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:one]; if (! cls) cls = meta->_genericCls; // for xcode code coverage } NSObject *newOne = [cls new]; / / into the familiar parsing / / reference above [newOne yy_modelSetWithDictionary: one]; if (newOne) [objectArr addObject:newOne]; } } ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr); } else {// In this case, If ([value isKindOfClass:[NSArray class]]) {if (meta->_nsType == YYEncodingTypeNSArray) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSArray *)value).mutableCopy); }} / /... } } break; / /... }}Copy the code

github repo