preface
In the above “Reveal the magic of YYModel (1)”, the main analysis of THE source structure of YYModel, and share the interesting implementation details of YYClassInfo and NSObject+YYModel.
Following the previous chapter, this article will interpret THE SOURCE code of YYModel on JSON model conversion, aiming to reveal the magic of AUTOMATIC transformation of JSON model.
The index
- Convert JSON to Model
- conclusion
Convert JSON to Model
JSON(JavaScript Object Notation) is a lightweight data interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is based on JavaScript Programming Language, a subset of Standard ECMA-262 3rd Edition – December 1999. JSON uses a completely language-independent text format, but also uses conventions similar to the C language family (C, C++, C#, Java, JavaScript, Perl, Python, etc.). These features make JSON an ideal data exchange language. Click here to learn more about JSON.
Model is the Object in Object Oriented Programming (OOP). OOP takes the Object as the basic unit of the program. An Object contains data and functions that manipulate data. Typically, objects are created based on business requirements, and in some design patterns (MVC, etc.) objects are used as models, or object modeling.
The conversion between JSON and Model can be divided into two types according to the conversion direction:
- JSON to Model
- Model to JSON
JSON to Model
Let’s start with the interface of YYModel.
+ (instancetype)yy_modelWithJSON:(id)json {
// Convert json to dictionary DIC
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
// use dic to retrieve the model
return [self yy_modelWithDictionary:dic];
}
Copy the code
The JSON to Model interface is simply divided into two subtasks:
- JSON to NSDictionary
- NSDictionary to Model
JSON to NSDictionary
Let’s first look at how _yy_dictionaryWithJSON converts JSON to NSDictionary.
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
// select null
if(! json || json == (id)kCFNull) return nil;
NSDictionary *dic = nil;
NSData *jsonData = nil;
// Operate according to the json type
if ([json isKindOfClass:[NSDictionary class]]) {
// If it is an NSDictionary class, the assignment is direct
dic = json;
} else if ([json isKindOfClass:[NSString class]]) {
// If it is an NSString class, use utF-8 to convert NSData
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
} else if ([json isKindOfClass:[NSData class]]) {
// If it is NSData, it is directly assigned to jsonData
jsonData = json;
}
// jsonData is not nil, which represents one of the cases 2 or 3 above
if (jsonData) {
// Use NSJSONSerialization to convert jsonData to DIC
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
// Determine the conversion result
if(! [dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
return dic;
}
Copy the code
This function mainly determines how to convert an input parameter to type NSDictionary based on its type and returns it.
Where kCFNull is the singleton object of CFNull in CoreFoundation. Like NSNull in the Foundation framework, CFNull is used to represent NULL values in collection objects (NULL is not allowed). CFNull objects are neither created nor destroyed, but are used by defining a CFNull constant, kCFNull, when null values are required.
Official documents: The CFNull opaque type defines a unique object used to represent null values in collection objects NULL values). CFNull objects are neither created nor destroyed. Instead, A single CFNull constant object – kCFNull – is defined and is used wherever a null value is needed.
NSJSONSerialization is an object used to convert JSON and equivalent Foundation objects to and from each other. It is thread-safe after iOS 7 and macOS 10.9 inclusive.
NSUTF8StringEncoding is used to convert NSString to NSData in the code, where the encoding type must be one of the five supported encoding types listed in the JSON specification:
- UTF-8
- UTF-16LE
- UTF-16BE
- UTF-32LE
- UTF-32BE
The most efficient encoding for parsing is UTF-8, so NSUTF8StringEncoding is used here.
The data must be in one of The 5 supported encodings listed in The JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
NSDictionary to Model
Now we’ll explore how yy_modelWithDictionary converts NSDictionary into Model from the yy_modelWithJSON interface.
On the blackboard! Get ready, this section introduces the best of YYModel code.
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
// Check the input parameter
if(! dictionary || dictionary == (id)kCFNull) return nil;
if(! [dictionary isKindOfClass:[NSDictionary class]]) return nil;
// Generate a _YYModelMeta model metaclass using the current class
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
/ / here _hasCustomClassFromDictionary identifies whether you need a custom return to class
// This is a model transformation add-on that can be used without too much attention
if(modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ? : cls; }// call yy_modelSetWithDictionary to assign a value to the newly created class instance one, and return one if the assignment succeeds
NSObject *one = [cls new];
// So we should focus on yy_modelSetWithDictionary in this function
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
Copy the code
Code according to _hasCustomClassFromDictionary identity determine whether need to customize the return type of the model. This code belongs to YYModel’s additional functions, in order not to distract you, here is only a brief introduction.
If we want to create different types of instances depending on the situation during JSON to Model, we can implement interfaces in Model:
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
Copy the code
To meet the demand. When yuan initialization model to test whether the current model classes can response the interface above, if you can response will put _hasCustomClassFromDictionary logo to YES, so will appear the code above:
if(modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ? : cls; }Copy the code
Well ~ I think these additional things in reading the source code to a large extent will distract our attention, the first detailed explanation, later encountered similar code we will skip, the internal implementation is mostly the same as the above case principle, interested students can study ha.
We should focus on yy_modelSetWithDictionary. This function (which is also the interface exposed by NSObject+YYModel) is an implementation of initializing the model according to the dictionary. It’s a long code, so you can skip it if you don’t want to see it. It’s explained later.
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
// Check the input parameter
if(! dic || dic == (id)kCFNull) return NO;
if(! [dic isKindOfClass:[NSDictionary class]]) return NO;
// Generate the _YYModelMeta model metaclass from its own class
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
// If the number of model metaclass key-value mappings is 0, return NO, indicating that the build failed
if (modelMeta->_keyMappedCount == 0) return NO;
/ / ignore, the logo corresponding modelCustomWillTransformFromDictionary interface
if (modelMeta->_hasCustomWillTransformFromDictionary) {
/ / similar modelCustomTransformFromDictionary interface, this interface is invoked before model transformation
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if(! [dic isKindOfClass:[NSDictionary class]]) return NO;
}
// Initialize the model setting context ModelSetContext
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void(*)self);
context.dictionary = (__bridge void *)(dic);
// Determine the relationship between the number of model metkey-value mappings and the number of JSON dictionaries
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
// Usually they are equal in number
// Special cases such as attribute elements that map multiple keys in the dictionary
/ / for each key value of the dictionary to call ModelSetWithDictionaryFunction
/ / this sentence is the core code, under normal circumstances is ModelSetWithDictionaryFunction through setting model dictionary
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
// Determine whether there is a mapping keyPath attribute element in the model
if (modelMeta->_keyPathPropertyMetas) {
/ / keyPath attributes for each mapping yuan ModelSetWithPropertyMetaArrayFunction execution
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0.CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// Determine if there is an attribute element mapping multiple keys in the model
if (modelMeta->_multiKeysPropertyMetas) {
/ / for each mapping several key properties of yuan ModelSetWithPropertyMetaArrayFunction execution
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0.CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); }}else { // If the number of model key value mappings is small, it is considered that there is no attribute element that maps multiple keys
/ / RMB each modelMeta attribute execute ModelSetWithPropertyMetaArrayFunction directly
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
/ / ignore, the logo modelCustomTransformFromDictionary corresponding interface
if (modelMeta->_hasCustomTransformFromDictionary) {
// This interface is used to do additional logic processing when the default JSON to Model object is not suitable
// We can also use this interface to validate the results of model transformations
return(((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
Copy the code
The code has been marked with necessary Chinese annotations. We will not talk more about the two custom extension interfaces. Because the code is quite long, let’s first sort out what yy_modelSetWithDictionary mainly does.
- Into the reference check
- Initialize model elements and map table validation
- Initialize the model setting context
ModelSetContext
- Is called for each key-value pair in the dictionary
ModelSetWithDictionaryFunction
- Verify the conversion result
The ModelSetContext is a structure that contains model elements, model instances, and dictionaries to be transformed.
typedef struct {
void *modelMeta; Yuan / / / < model
void *model; ///< model instance, pointing to the output model
void *dictionary; ///< dictionary to be converted
} ModelSetContext;
Copy the code
You must have noticed the ModelSetWithDictionaryFunction function, regardless of which the right branch of logic, finally put the dictionary is call this function key (keypath value out of the corresponding) and assigned to the Model, So let’s look at the implementation of this function.
// Dictionary key-value pair modeling
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
// Get the input context
ModelSetContext *context = _context;
// Fetch the context model element
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
// Retrieve the mapping table attribute element from the model element according to the input parameter _key
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
// Get the assigned model
__unsafe_unretained id model = (__bridge id)(context->model);
// Iterate over propertyMeta until propertyMeta->_next == nil
while (propertyMeta) {
// Call ModelSetValueForProperty if propertyMeta has setter methods
if (propertyMeta->_setter) {
// Core methods
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
Copy the code
ModelSetWithDictionaryFunction function implementation logic is assignment model through model set context to get the belt first, then traverse the current property yuan (until propertyMeta – > _next = = nil), Find a property element whose setter is not empty and assign it to the ModelSetValueForProperty method.
ModelSetValueForProperty function is the realization method of assigning values to attributes in the model, which is also the core code of the whole YYModel. Don’t worry, this function is written in a friendly way. It’s only about 300 lines 😜 (I’ll try to ignore anything that doesn’t matter), but if you ignore too many lines, it will affect the continuity of the code.
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
// If the attribute is a CNumber, enter int, uint...
if (meta->_isCNumber) {
// Assign after converting to NSNumber
NSNumber *num = YYNSNumberCreateFromID(value);
// Where ModelSetNumberToProperty encapsulates assigning an NSNumber to an attribute element
ModelSetNumberToProperty(model, num, meta);
if (num) [num class]; // hold the number
} else if (meta->_nsType) {
// If the attribute is nsType, that is, NSString, NSNumber...
if (value == (id)kCFNull) { // If null, assign nil (via the property meta-_setter method using objc_msgSend)
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else { / / not be empty
switch (meta->_nsType) {
/ / or NSMutableString nsstrings
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
// Handle possible value types: NSString, NSNumber, NSData, NSURL, NSAttributedString
// The corresponding branch is to convert value to NSString or NSMutableString and then call setter assignment. }break;
NSValue, NSNumber or NSDecimalNumber
case YYEncodingTypeNSValue:
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber: {
// Assign a value to the type of the attribute element (this may involve converting between types). }break;
/ / NSData or NSMutableData
case YYEncodingTypeNSData:
case YYEncodingTypeNSMutableData: {
// Assign a value to the type of the attribute element (this may involve converting between types). }break;
// NSDate
case YYEncodingTypeNSDate: {
// Consider possible value types: NSDate or NSString
// Convert to NSDate and assign. }break;
// NSURL
case YYEncodingTypeNSURL: {
// Consider possible value types: NSURL or NSString
// Convert to NSDate and assign nil. }break;
/ / NSArray or NSMutableArray
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
// Generic judgment of attribute elements
if (meta->_genericCls) { // If there are generics
NSArray *valueArr = nil;
// If value belongs to an NSSet class, the value is assigned to NSArray
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
// Iterate over the valueArr just converted from value
if (valueArr) {
NSMutableArray *objectArr = [NSMutableArray new];
for (id one in valueArr) {
// Add the element in valueArr to objectArr
if ([one isKindOfClass:meta->_genericCls]) {
[objectArr addObject:one];
} else if ([one isKindOfClass:[NSDictionary class]]) {
// The element in valueArr is a dictionary class,
Class cls = meta->_genericCls;
/ / ignore
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if(! cls) cls = meta->_genericCls;// for xcode code coverage
}
// Remember our direct starting point yy_modelSetWithDictionary, which converts the dictionary to the model
// I think this is an indirect recursive call
// If the design model is infinite recursion (once upon a time there was a mountain, there was a temple on the mountain), then it will be slow
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
// The successful conversion machine also joins objectArr
if(newOne) [objectArr addObject:newOne]; }}// Finally assign the resulting objectArr to the property
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, objectArr); }}else {
// There are no generics. ~ Determine whether value can be of type NSArray or NSSet
// Convert assignment (involving mutable). }}break;
/ / or NSDictionary NSMutableDictionary
case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
// Similar to array handling above, generic types are indirect recursion and non-generic types are mutable. }break;
/ / NSSet or NSMutableSet
case YYEncodingTypeNSSet:
case YYEncodingTypeNSMutableSet: {
// Similar to array handling above, generic types are indirect recursion and non-generic types are mutable. }default: break; }}}else { // Attribute elements do not belong to CNumber or nsType
BOOL isNull = (value == (id)kCFNull);
switch (meta->_type & YYEncodingTypeMask) {
// id
case YYEncodingTypeObject: {
if (isNull) { // null, assign nil
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else if([value isKindOfClass:meta->_cls] || ! meta->_cls) {// Attribute element and value belong to the same class
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, (id)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
// ~ value is subordinate
NSObject *one = nil;
// If the attribute element has a getter method, the instance is obtained through the getter
if (meta->_getter) {
one = ((id(*) (id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
}
if (one) {
// Use yy_modelSetWithDictionary to output the attribute instance object
[one yy_modelSetWithDictionary:value];
} else {
Class cls = meta->_cls;
/ / skip
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value];
if(! cls) cls = meta->_genericCls;// for xcode code coverage
}
// Use yy_modelSetWithDictionary to output the attribute instance object and assign the value
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void(*) (id, SEL, id(a))void *) objc_msgSend)((id)model, meta->_setter, (id)one); }}}break;
// Class
case YYEncodingTypeClass: {
if (isNull) { // NULL, assigns (Class)NULL, since Class is actually a structure defined by C language, so NULL is used
// Horizontal comparisons of nil, nil, NULL, NSNull, kCFNull are presented separately below
((void(*) (id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
} else {
// determine the possible type of value NSString or class_isMetaClass(object_getClass(value))
// If the condition is met, the value is assigned. }}break;
// SEL
case YYEncodingTypeSEL: {
// set SEL to NULL
SEL = NSSelectorFromString(value); Then the assignment. }break;
// block
case YYEncodingTypeBlock: {
Void (^)())NULL
/ / or judgment type [value isKindOfClass: YYNSBlockClass ()] after the assignment. }break;
Struct, union, char[n]; // struct, union, char[n]
// Union community, similar to struct existence, but each member of the union will use the same storage space, can only store information of the last member
case YYEncodingTypeStruct:
case YYEncodingTypeUnion:
case YYEncodingTypeCArray: {
if ([value isKindOfClass:[NSValue class]]) {
// Type Encodings is involved
const char *valueType = ((NSValue *)value).objCType;
const char *metaType = meta->_info.typeEncoding.UTF8String;
// Compare valueType and metaType to see if they are the same, if they are (STRCMP (a, b) returns 0)
if (valueType && metaType && strcmp(valueType, metaType) == 0) { [model setValue:value forKey:meta->_name]; }}}break;
/ / void * or char *
case YYEncodingTypePointer:
case YYEncodingTypeCString: {
if (isNull) { // Assign (void *)NULL
((void(*) (id, SEL, void(*))void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
} else if ([value isKindOfClass:[NSValue class]]) {
// Type Encodings is involved
NSValue *nsValue = value;
if (nsValue.objCType && strcmp(nsValue.objCType, "^v") = =0) {((void(*) (id, SEL, void(*))void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue); }}}default: break; }}}Copy the code
Well 😓 I really have ignored a lot of code, but it’s still a bit long. In fact, the code logic is still very simple, but the model assignment involves more trivial logic such as code type, resulting in a relatively large amount of code. Let’s sum up the implementation logic of the core code together.
- Partitioning code logic by attribute metatype
- If the attribute element is of type CNumber, that is, int, uint, etc
ModelSetNumberToProperty
The assignment - If the attribute element is of type NSType, that is, NSString, NSNumber, etc., then the logical judgment is made and the value is assigned according to the corresponding type that may be involved in the type conversion (see the implementation logic in the above code).
- If the attribute element does not belong to CNumber and NSType, the guess is id, Class, SEL, Block, struct, Union, char[n], void*, or char* and the corresponding conversion and assignment is made
Well ~ in fact, the above code in addition to long logic is very simple, summed up is according to the type of possible to make the corresponding logic operation, you have time to read the source code, especially their own project using YYModel students. I believe that after reading the YYModel attribute assignment will be clear, so that in the use of YYModel in the daily occurrence of any problem can know, change the code naturally such as god helped ha.
Er… Considering the large amount of code in the whole process of NSDictionary to Model, I spent some time to summarize its logic into a diagram:
I hope I can do my best to make the expression of the article more straightforward.
Model to JSON
Model to JSON is simpler than JSON to Model. NSJSONSerialization has some rules for converting JSON:
- The top-level object is of type NSArray or NSDictionary
- All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull
- The key in all dictionaries is an NSString instance
- Numbers is any representation other than infinity and NaN
Note: The above is from NSJSONSerialization official document.
With this in mind, we can begin to interpret the source code from yy_modelToJSONObject, the Model-to-JSON interface of YYModel.
- (id)yy_modelToJSONObject {
// Recursively convert the model to JSON
id jsonObject = ModelToJSONObjectRecursive(self);
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
return nil;
}
Copy the code
~ a total of 4 lines of code, the need to focus on only one of the first line of code ModelToJSONObjectRecursive method, Objective – C language features determines the comments from the function name can not read the code, This method, as the name suggests, converts the Model to JSON recursively.
// Recursively convert model to JSON, return nil if conversion is abnormal
static id ModelToJSONObjectRecursive(NSObject *model) {
// Nulls or objects that can be returned directly
if(! model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;
// If model belongs to NSDictionary
if ([model isKindOfClass:[NSDictionary class]]) {
Return if it can be converted directly to JSON data
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableDictionary *newDic = [NSMutableDictionary new];
// Iterate over the key and value of the model(((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *stringKey = [key isKindOfClass:[NSString class]]? key : key.description;if(! stringKey)return;
// Recursively parse value
id jsonObj = ModelToJSONObjectRecursive(obj);
if(! jsonObj) jsonObj = (id)kCFNull;
newDic[stringKey] = jsonObj;
}];
return newDic;
}
// If model belongs to NSSet
if ([model isKindOfClass:[NSSet class]]) {
// If the JSON object can be converted directly, it is returned directly
// Otherwise iterate, recursively parsing as needed. }if ([model isKindOfClass:[NSArray class]]) {
// If the JSON object can be converted directly, it is returned directly
// Otherwise iterate, recursively parsing as needed. }// Handle NSURL, NSAttributedString, NSDate, and NSData accordingly
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;
// Initialize a model element with [model class]
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]].// If the mapping table is empty, nil is returned without parsing
if(! modelMeta || modelMeta->_keyMappedCount ==0) return nil;
// Performance tuning details, __unsafe_unretained to avoid unnecessary retain and release overhead of using the result pointer directly across the block
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
__unsafe_unretained NSMutableDictionary *dic = result;
// Iterate over the model meta-attribute mapping dictionary
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
// If there is no getter for traversing the current property, skip
if(! propertyMeta->_getter) return;
id value = nil;
// If the attribute element belongs to CNumber, that is, its type is int, float, double, etc
if (propertyMeta->_isCNumber) {
// Get the corresponding value from the property using the getter method
value = ModelCreateNumberFromProperty(model, propertyMeta);
} else if (propertyMeta->_nsType) { // Attribute elements belong to an nsType, such as NSString
// Use the getter method to get the value
id v = ((id(*) (id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
// Parse the value recursively
value = ModelToJSONObjectRecursive(v);
} else {
// Do the corresponding processing according to the type of attribute element
switch (propertyMeta->_type & YYEncodingTypeMask) {
// id, requires recursive resolution, returns nil if resolution fails
case YYEncodingTypeObject: {
id v = ((id(*) (id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
// Class, go to NSString, return the Class name
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
// SEL, go to NSString, return the string representation of the given SEL
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break; }}// If value still fails to be resolved, skip it
if(! value)return;
// The current attribute element is KeyPath mapping, A.B.C, etc
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
// _mappedToKeyPath is an array of strings that A.B.C splits into '.' strings iterating over _mappedToKeyPath
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
// Loop through to the end
if (i + 1 == max) {
// If the key at the end is nil, value is assigned
if(! superDic[key]) superDic[key] = value;break;
}
// use subDic to get the current key
subDic = superDic[key];
// If subDic exists
if (subDic) {
// If subDic belongs to NSDictionary
if ([subDic isKindOfClass:[NSDictionary class]]) {
// Assign the mutable version of subDic to superDic[key]
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break; }}else {
// Assign NSMutableDictionary to superDic[key]
// Note that there is a reason for using subDic as an indirect assignment
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
// superDic points to subDic, which can be parsed layer by layer as _mappedToKeyPath is traversed
// This is why we first converted subDic to NSMutableDictionary
superDic = subDic;
subDic = nil; }}else {
// Check dic[propertyMeta->_mappedToKey] if it is not KeyPath, and assign value if it is nil
if(! dic[propertyMeta->_mappedToKey]) { dic[propertyMeta->_mappedToKey] = value; }}}];/ / ignore, corresponding modelCustomTransformToDictionary interface
if (modelMeta->_hasCustomTransformToDictionary) {
// Used to provide custom additional procedures when the default Model-to-JSON procedure does not fit the current Model type
// You can also use this method to verify the conversion results
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
if(! suc)return nil;
}
return result;
}
Copy the code
Er… The code is still a bit long, but compared to yy_modelSetWithDictionary in the JSON to Model direction, ModelSetWithDictionaryFunction ModelSetValueForProperty and three methods of indirect recursion is one of the very simple said, then sum up the above code logic.
- Determine the input parameter, if the condition can be returned directly
- If the Model is subordinate to NSType, the logic is handled depending on the type
- If the above conditions are not met, a Model meta-_yyModelMeta is initialized with the Model’s Class
- Judge the mapping relationship of model elements, traverse the mapping table to get the corresponding key-value pair, save it in the dictionary and return it
Note: __unsafe_unretained dic points to NSMutableDictionary *result as a result of our return. // Avoid retain and release in block to save overhead by using result directly to iterate through unnecessary retain and release operations later in the map block.
conclusion
- This article follows the explanation of YYModel code structure in the above “Revealing the magic of YYModel (1)” and then focuses on the realization logic of JSON model conversion.
- Divided from the direction of the JSON model conversion, the positive and negative directions of THE YYModel conversion process are analyzed and revealed, hoping to solve the confusion of the automatic conversion of THE JSON model.
The article is written with great care (it is my personal original article, please indicate lision. Me/for reprinting). If any mistake is found, it will be updated in my personal blog first.
If you have any comments on the article, please directly contact me on my microblog @lision (because there are too many notifications after the community posts, I have closed these pushes and only left them on my microblog, which is cold ~~~~(>_<)~~~~).
Supplement ~ I set up a technical exchange wechat group, want to know more friends in it! If you have any questions about the article or encounter some small problems in your work, you can find me or other friends in the group to communicate and discuss. Looking forward to your joining us
Emmmmm.. Because the number of wechat group is over 100, it is not possible to scan the code into the group, so please scan the qr code above to pay attention to the public number into the group.