YYModel, model dictionary, automatic conversion,

Model to dictionary, mainly through the runtime, take out the attribute name of the model, recursive construction of dictionary

YYModel designed four models to record the information of classes and their attributes.

  • Class + information record

  • class

  • Property + information record

  • attribute

Two for classes and two for attributes

If you include it from top to bottom, it’s pretty clear

YYModel in order to facilitate logical processing, four models include crossover, a little round

This article turns the model into a dictionary and takes a look at its operation

Call:
    NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; 
    Book * page = [Book yy_modelWithJSON: jsonData];
    NSDictionary * dict = [page yy_modelToJSONObject];
Copy the code
Implementation:
- (id) yy_modelToJSONObject {/ / remove the json id jsonObject = ModelToJSONObjectRecursive (self); If ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject; If ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject; return nil; }Copy the code

The model is recursively processed

/ / to get the model, calculate the static dictionary id ModelToJSONObjectRecursive (NSObject * model) {/ / was not found, it returns the if (! model || model == (id)kCFNull) return model; If ([model isKindOfClass:[NSString class]]) return model; if ([model isKindOfClass:[NSNumber class]]) return model; / /... If ([Model isKindOfClass:[NSURL class]]) return ((NSURL *)model). AbsoluteString; if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string; if ([model isKindOfClass:[NSDate class]]) return @"ha ha"; if ([model isKindOfClass:[NSData class]]) return nil; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]]; if (! modelMeta) return nil; NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; __unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {if (! PropertyMeta ->_getter) return; // Get value id value = nil; if (propertyMeta->_isCNumber) {value = ModelCreateNumberFromProperty(model, } else if (propertyMeta->_nsTypeX) {// _nsTypeX is the default object id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); } else { switch (propertyMeta->_typeA & YYEncodingTypeMask) { case YYEncodingTypeObject: {// Handle custom object id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); Recursive processing value = ModelToJSONObjectRecursive (v); the 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: If (! Dic [propertyMeta->_mappedToKey]) {// Set dic[propertyMeta->_mappedToKey] = value;} }]; return result; }Copy the code

Among them,

if (propertyMeta->_nsTypeX)
Copy the code

If _nsTypeX does not exist, then _nsTypeX = 0, YYEncodingTypeNSUnknown

With the following code,

If _nsTypeX is NSString, NSNumber, NSValue, NSData, NSArray, NSDictionary, it is processed recursively

static YYEncodingNSType YYClassGetNSType(Class cls){ if (! cls) return YYEncodingTypeNSUnknown; if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString; if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber; if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue; if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData; if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray; if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary; return YYEncodingTypeNSUnknown; }Copy the code

runtime:

@interface NSObject (Model)

Extend NSObject so model can be called directly

  • - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic Method,
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
Copy the code

@interface _YYModelMeta: NSObject

Class information and add custom information

Class method first, then instance method

Class method lock, do cache

Instance method that handles properties

  • + (instancetype)metaWithClass:(Class)clsMethod,
meta = [[_YYModelMeta alloc] initWithClass:cls];
Copy the code
  • - (instancetype)initWithClass:(Class)clsMethod,
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
Copy the code

@interface YYClassInfo : NSObject

The class information

Class method first, then instance method

Class method lock, do cache

Instance method that handles properties

  • + (instancetype)classInfoWithClass:(Class)clsMethod,
info = [[YYClassInfo alloc] initWithClass:cls];
Copy the code
  • - (instancetype)initWithClass:(Class)cls Method,
- (instancetype)initWithClass:(Class)cls { // ... unsigned int propertyCount = 0; Objc_property_t *properties = class_copyPropertyList(CLS, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; I++) {/ / instantiate, every attribute YYClassPropertyInfo * info = [[YYClassPropertyInfo alloc] initWithProperty: properties [I]]. if (info.name) propertyInfos[info.name] = info; } free(properties); } / /... return self; }Copy the code

@interface YYClassPropertyInfo : NSObject

Attribute information

- (instancetype)initWithProperty:(objc_property_t)property { if (! property) return nil; self = [super init]; const char *name = property_getName(property); If (name) {/ / get the property name, made a UTF8 encoding _name = [nsstrings stringWithUTF8String: name]. } YYEncodingType type = 0; unsigned int attrCount; Objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); for (unsigned int i = 0; i < attrCount; Switch (attrs[I].name[0]) {case 'T': {/ / Type encoding the if (attrs [I] value) {/ / get the encoding _typeEncoding = [nsstrings stringWithUTF8String: attrs [I] value]; type = YYEncodingGetType(attrs[i].value); if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) { NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; if (! [scanner scanString:@"@\"" intoString:NULL]) continue; NSString *clsName = nil; if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString: @ "\" < "] intoString: & clsName]) {/ / to get the information if _clsX = (clsName. Length) objc_getClass(clsName.UTF8String); } // _protocols // ... } } } break; / /... }} / /... return self; }Copy the code

@interface _YYModelPropertyMeta: NSObject

Attribute information, and attribute addition classes

+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic { // ... _YYModelPropertyMeta *meta = [self new]; Meta ->_name = propertyinfo.name; meta->_name = propertyinfo.name; meta->_typeA = propertyInfo.type; meta->_info = propertyInfo; / /... If ((meta->_typeA & YYEncodingTypeObject) == YYEncodingTypeObject) {meta->_nsTypeX = YYClassGetNSType(propertyInfo.clsX); } else { meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_typeA); }Copy the code
The model-to-dictionary part is handled by recording the property type

It’s a number, straight away

Non-numeric, system type takes precedence

The final case is dealing with objects and so on

Related 💦 article

YYModel source code analysis: model design

YYModel source code analysis: dictionary to model

Related 💦 💧 repo

🌊 repo