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)cls
Method,
meta = [[_YYModelMeta alloc] initWithClass:cls];
Copy the code
- (instancetype)initWithClass:(Class)cls
Method,
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)cls
Method,
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