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 contextModelSetContext
  • Is called for each key-value pair in the dictionaryModelSetWithDictionaryFunction
  • 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, etcModelSetNumberToPropertyThe 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.