Mantle is a model framework developed by Github. The main function of Mantle is to make it easier for developers to build the model layer of applications. This paper mainly introduces the most commonly used JSON function in Mantle — dictionary to model.

The use of the Mantle

Firstly, take an API of Zhihu as an example to explain how to use Mantle to build model for this API.


API data structure

  1. Build corresponding models according to Mantle requirements (Mantle requires that all models inherit from MTLJSONSerializing protocol)

Build the ZhihuLatestNews Model corresponding to the JSONKeyPath returned




ZhihuLatestNews.h




ZhihuLatestNews.m

Build the ZhihuStory Model corresponding to the JSONkeyPath of SOtries




ZhihuStory.h




ZhihuStory.m

Build the ZhihuStory Model corresponding to the JSONkeyPath of top_SOtries




ZhihuTopStory.h




ZhihuTopStory.m

2. Convert the result of network request into model using Manlte


Manlte use

Mantle of the interface

Mantle realized the transformation between dictionary and model through MTLJSONAdapter

ZhihuLatestNews *lateNews = [MTLJSONAdapter modelOfClass:ZhihuLatestNews.class fromJSONDictionary:dict error:&merror];
Copy the code

Mantle’s core procedures

1. Get model properties –> JSONKeyPath mapping dictionary 2. Obtain the attribute list of model 3. Convert the value in the JSON dictionary returned in the network request according to model’s method 4. Assign values to model properties using KVC to complete the operation

Mantle’s JSON dictionary –> Model method call hierarchy,Mantle source code interpretation process is mainly reference to this call process

| + [MTLJSONAdapter modelOfClass: fromJSONDictionary: error:] / / Mantle call entrance | [0 < MTLJSONAdapter x7fe68bd64340 > InitWithModelClass:] / / create MTLJSONAdapter | + [ZhihuLatestNews JSONKeyPathsByPropertyKey] / / retrieve attributes - > | JSONKeyPath mapping dictionary + [ZhihuLatestNews propertyKeys] / / get all the Mantle of model available properties | + [ZhihuLatestNews enumeratePropertiesUsingBlock:] | + [ZhihuLatestNews storageBehaviorForPropertyWithKey:] / / judge whether the properties of the model Mantle requirements | the From: | - [MTLJSONAdapter initWithModelClass:] + [MTLJSONAdapter valueTransformersForModelClass:] / / value transformation | + [ZhihuLatestNews propertyKeys] | +[MTLJSONAdapter transformerForModelPropertiesOfClass:] | From: +[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:] | [<MTLJSONAdapter 0x7fe68bd64340> ModelFromJSONDictionary: error:] | + [ZhihuLatestNews propertyKeys] / / get all the Mantle of model available properties | + [ZhihuLatestNews ModelWithDictionary: error:] / / generated model objects | [0 < ZhihuLatestNews x7fe68bf2dea0 > initWithDictionary: error:] | [<ZhihuLatestNews 0x7fe68bf2dea0> init] | [<ZhihuLatestNews 0x7fe68bf2dea0> setDate:] | [<ZhihuLatestNews 0x7fe68bf2dea0> validate:] | +[ZhihuLatestNews propertyKeys]Copy the code

Mantle source code interpretation

1. Initialize the MTLJSONAdapter

The MTLJSONAdapter is an adapter between the dictionary and the Model that converts the JSON dictionary into the model object of the application




Method definition

+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error { / / initialize MTLJSONAdapter MTLJSONAdapter * adapter = [[self alloc] initWithModelClass: modelClass]; return [adapter modelFromJSONDictionary:JSONDictionary error:error]; }Copy the code

2. InitWithModelClass :(Class) the purpose of modelClass is to initialize the MTLJSONAdapter with the given modelClass


Method definition

- (id)initWithModelClass:(Class)modelClass {// nullassert (modelClass! = nil); // Whether to implement MTLJSONSerializing protocol Verify NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]); self = [super init]; if (self == nil) return nil; // Use a variable to save the modelClass so that _modelClass = modelClass can be used later; / / gets an attribute value and JSONKeyPaths mapping dictionary, and use _JSONKeyPathsByPropertyKey save / / JSONKeyPathsByPropertyKey is MTLJSONSerializing protocol defined in a way, In MTLModel subclass implementation _JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey]; ** NSSet *propertyKeys = [self.modelClass propertyKeys]; / / determine whether _JSONKeyPathsByPropertyKey contained in propertyKeys, / / used to confirm _JSONKeyPathsByPropertyKey inside the key is the attribute of the model for * (nsstrings mappedPropertyKey in _JSONKeyPathsByPropertyKey) {  if (! [propertyKeys containsObject:mappedPropertyKey]) { NSAssert(NO, @"%@ is not a property of %@.", mappedPropertyKey, modelClass); return nil; } / / out according to the model attribute key JSONKeyPath id value = _JSONKeyPathsByPropertyKey [mappedPropertyKey]; / / TODO [value isKindOfClass: NSArray class] judge what is this? Where do you use it? At the end of the article 1 if ([value isKindOfClass: NSArray. Class]) {for (nsstrings * keyPath value) in {if ([keyPath isKindOfClass:NSString.class]) continue; NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.", mappedPropertyKey, value); return nil; } } else if (! [value isKindOfClass:NSString.class]) { NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.",mappedPropertyKey, value); return nil; }} / / model attributes valueTransformers for type conversion / / * * this method after the article has a detailed interpretation of * * _valueTransformersByPropertyKey = [self. Class valueTransformersForModelClass:modelClass]; //A new map table object which has strong references to the keys and values. _JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable]; return self; }Copy the code

3.MTLModel propertyKeys class method, which returns an NSSet containing a list of Model properties, but this NSSet does not contain readonly modified properties, and does not contain ivars variable properties. And properties of the MTLModel class itself. Anything else that’s declared at sign property is going to be in the NSSet data,


Method definition

/// Returns the keys for all @property declarations, except for `readonly` /// properties without ivars, Or properties on MTLModel itself. + (NSSet *)propertyKeys { If there is a direct return NSSet * cachedKeys = objc_getAssociatedObject (self, MTLModelCachedPropertyKeysKey); if (cachedKeys ! = nil) return cachedKeys; NSMutableSet *keys = [NSMutableSet set]; // Run the following command to check the attributes of the model: Join keys variable / / * * this method after the article has a detailed interpretation of * * [self enumeratePropertiesUsingBlock: ^ (objc_property_t property, BOOL *stop) {NSString *key = @(property_getName(property)); Namely not MTLPropertyStorageNone can do mapping / / * * this method after the article has a detailed interpretation of * * the if ([self storageBehaviorForPropertyWithKey: key]! = MTLPropertyStorageNone) { [keys addObject:key]; } }]; // It doesn't really matter if we replace another thread's work, Since we do // it atomically and the result should be the same. Objc_setAssociatedObject (self, MTLModelCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY); return keys; }Copy the code

4. MTLModel enumeratePropertiesUsingBlock class methods, this method is used to traverse the model properties

+ (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block { Class cls = self; BOOL stop = NO; // The model inheritance hierarchy traverses the model attributes while (! stop && ! [cls isEqual:MTLModel.class]) { unsigned count = 0; Objc_property_t *properties = class_copyPropertyList(CLS, &count); cls = cls.superclass; if (properties == NULL) continue; @onExit { free(properties); }; //block callback for (unsigned I = 0; i < count; i++) { block(properties[i], &stop); if (stop) break; }}}Copy the code

5. MTLModel storageBehaviorForPropertyWithKey class method, is used to judge whether the properties of the model can be used to do the conversion


Method definition

+ (MTLPropertyStorage) storageBehaviorForPropertyWithKey: (propertyKey nsstrings *) {/ / according to the property name attribute for objc_property_t related content property = class_getProperty(self.class, propertyKey.UTF8String); if (property == NULL) return MTLPropertyStorageNone; ** mtl_propertyAttributes *attributes = ** mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property); @onExit { free(attributes); }; / / is there a setter and getter methods BOOL hasGetter = [self instancesRespondToSelector: attributes - > getter]; BOOL hasSetter = [self instancesRespondToSelector:attributes->setter]; if (! attributes->dynamic && attributes->ivar == NULL && ! hasGetter && ! HasSetter) {// Attributes are not dynamic (@dynamic is to tell the compiler that the getters and setters for attributes modified by @dynamic in code are bound dynamically at runtime or otherwise, // Attributes ->ivar empty // No getter method // No setter method return MTLPropertyStorageNone; } else if (attributes->readonly && Attributes ->ivar == NULL) {//attributes is readonly //attributes->ivar variable is empty if ([self] IsEqual: mtlModel.class]) {return MTLPropertyStorageNone; } else {// Check superclass in case the subclass reclass declares a greater property that falls through // Prevent the superclass attribute be subclasses to declare the return [self. The superclass storageBehaviorForPropertyWithKey: propertyKey]; } } else { return MTLPropertyStoragePermanent; }}Copy the code

6.EXTRuntimeExtensions’ mtl_copyPropertyAttributes method, which transforms the Runtime representation of the property into a better-understood Mantle representation. This method is dull and relatively difficult to understand, so it can be skipped without affecting Mantle interpretation. This method needs to be combined with apple’s official Runtime development documentation and then a single step debugging to better understand.


Method definition

//以属性date为例,说明整个转化过程,具体的信息可以参考苹果官方的 runtime 文档
// @property (nonatomic,strong) NSString *date 

mtl_propertyAttributes *mtl_copyPropertyAttributes (objc_property_t property) {
    //The string starts with a T followed by the @encode type and a comma(逗号), and finishes with a V followed by the name of the backing instance variable.
    //属性 date 的 runtime 表示形式为 "T@\"NSString\",&,N,V_date"
    const char * const attrString = property_getAttributes(property);
    if (!attrString) {
        fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property));
        return NULL;
    }
    //必须以 T 开头
    if (attrString[0] != 'T') {
        fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property));
        return NULL;
    }
    //去掉 T 变成 "@\"NSString\",&,N,V_date"
    const char *typeString = attrString + 1;//attrString代表字符串的起始地址,地址加1表示字符串截取

    // Obtains the actual size and the aligned size of an encoded type.
    // 去掉 @encode 字符串 变成 ",&,N,V_date"
    const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
    if (!next) {
        fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }
   // 以属性 date 为例差11个字符
    size_t typeLength = next - typeString;
    if (!typeLength) {
        fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }

    // allocate enough space for the structure and the type string (plus a NUL)
    // 将propertyAttributes变成mtl_propertyAttributes类型
    // TODO 长度的计算?为什么是这样计算?
    mtl_propertyAttributes *attributes = calloc(1, sizeof(mtl_propertyAttributes) + typeLength + 1);
    if (!attributes) {
        fprintf(stderr, "ERROR: Could not allocate mtl_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }

    // copy the type string
    // 复制属性的类型
    strncpy(attributes->type, typeString, typeLength);
    attributes->type[typeLength] = '\0';//字符串结尾

    // if this is an object type, and immediately followed by a quoted string...
    if (typeString[0] == *(@encode(id)) && typeString[1] == '"') {
        // we should be able to extract a class name
        // "NSString\",&,N,V_date"
        const char *className = typeString + 2;//字符串截取

        //extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置。
        //"\",&,N,V_date"
        next = strchr(className, '"');

        if (!next) {
            fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
            return NULL;
        }

        if (className != next) {
            // 通过内存地址相减 0x0000000104f0347e((const char *) next) - 0x0000000104f03476((const char *) className) = 8
            size_t classNameLength = next - className;
            char trimmedName[classNameLength + 1];//创建用于存放属性类型的数组

            strncpy(trimmedName, className, classNameLength);//复制属性类型到trimmedName
            trimmedName[classNameLength] = '\0';//数组末尾结束符号

            // attempt to look up the class in the runtime
            attributes->objectClass = objc_getClass(trimmedName);//设置属性类型
        }
    }
     // "\",&,N,V_date"
    if (*next != '\0') {
        // skip past any junk before the first flag
        // ",&,N,V_date"
        next = strchr(next, ',');
    }

    while (next && *next == ',') {
        //第一次循环 &
        //第一次循环 N
        //第一次循环 V
        char flag = next[1];
        next += 2;

        switch (flag) {
        case '\0':
            break;

        case 'R':
            //The property is read-only (readonly).
            attributes->readonly = YES;
            break;

        case 'C':
            //The property is a copy of the value last assigned (copy).
            attributes->memoryManagementPolicy = mtl_propertyMemoryManagementPolicyCopy;
            break;

        case '&':
            //The property is a reference to the value last assigned (retain).
            attributes->memoryManagementPolicy = mtl_propertyMemoryManagementPolicyRetain;
            break;

        case 'N':
            //The property is non-atomic (nonatomic).
            attributes->nonatomic = YES;
            break;

        case 'G':
            //The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).
        case 'S':
            //The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).
            {
                const char *nextFlag = strchr(next, ',');
                SEL name = NULL;

                if (!nextFlag) {
                    // assume that the rest of the string is the selector
                    const char *selectorString = next;
                    next = "";

                    name = sel_registerName(selectorString);
                } else {
                    size_t selectorLength = nextFlag - next;
                    if (!selectorLength) {
                        fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
                        goto errorOut;
                    }

                    char selectorString[selectorLength + 1];

                    strncpy(selectorString, next, selectorLength);
                    selectorString[selectorLength] = '\0';

                    name = sel_registerName(selectorString);
                    next = nextFlag;
                }

                if (flag == 'G')
                    attributes->getter = name;
                else
                    attributes->setter = name;
            }

            break;

        case 'D':
            //The property is dynamic (@dynamic).
            attributes->dynamic = YES;
            attributes->ivar = NULL;
            break;

        case 'V':
            // assume that the rest of the string (if present) is the ivar name
            // V 之后的是变量名称
            if (*next == '\0') {
                // if there's nothing there, let's assume this is dynamic
                attributes->ivar = NULL;
            } else {
                //取得变量名称
                attributes->ivar = next;
                next = "";
            }

            break;

        case 'W':
             //The property is a weak reference (__weak).
            attributes->weak = YES;
            break;

        case 'P':
            //The property is eligible for garbage collection.
            attributes->canBeCollected = YES;
            break;

        case 't':
            fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property));

            // skip over this type encoding
            while (*next != ',' && *next != '\0')
                ++next;

            break;

        default:
            fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property));
        }
    }

    if (next && *next != '\0') {
        fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property));
    }

    if (!attributes->getter) {
        // use the property name as the getter by default
        //使用默认的 getter 方法
        attributes->getter = sel_registerName(property_getName(property));
    }

    if (!attributes->setter) {
        //使用默认的 setter 方法
        const char *propertyName = property_getName(property);
        size_t propertyNameLength = strlen(propertyName);

        // we want to transform the name to setProperty: style
        size_t setterLength = propertyNameLength + 4;

        char setterName[setterLength + 1];
        strncpy(setterName, "set", 3);
        strncpy(setterName + 3, propertyName, propertyNameLength);

        // capitalize property name for the setter
        setterName[3] = (char)toupper(setterName[3]);

        setterName[setterLength - 1] = ':';
        setterName[setterLength] = '\0';

        attributes->setter = sel_registerName(setterName);
    }

    return attributes;

errorOut:
    free(attributes);
    return NULL;
}
Copy the code

7. After obtaining the required properties of Mantle, it is necessary to do some transformation operations. MTLJSONAdapter valueTransformersForModelClass class method is mainly to JSONKeyPath value into the model of the type of attribute declarations

/ / do type transformation of value, value type conversion method provided by the model + (NSDictionary *) valueTransformersForModelClass modelClass: (Class) {/ / basic judgment NSParameterAssert(modelClass ! = nil); NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]); NSMutableDictionary *result = [NSMutableDictionary dictionary]; For (NSString *key in [modelClass propertyKeys]) {// if (NSString *key in [modelClass propertyKeys]) + < key > SEL JSONTransformer type method selector = MTLSelectorWithKeyPattern (key, "JSONTransformer"); / / determine whether have to implement this method if ([modelClass respondsToSelector: selector]) {/ / implementation of the method, Call this method and obtain the return value of this method IMP IMP = [modelClass methodForSelector: selector]; NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp; NSValueTransformer *transformer = function(modelClass, selector); // Save the NSValueTransformer object if (transformer! = nil) result[key] = transformer; continue; } //2, check whether the model implements +JSONTransformerForKey: Type method if ([modelClass respondsToSelector: @Selector (JSONTransformerForKey:)]) {NSValueTransformer *transformer = [modelClass JSONTransformerForKey:key]; // Save the NSValueTransformer object if (transformer! = nil) result[key] = transformer; continue; } objc_property_t property = class_getProperty(modelClass, key.UTF8String); if (property == NULL) continue; Mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property); @onExit { free(attributes); }; NSValueTransformer *transformer = nil; if (*(attributes->type) == *(@encode(id))) { Class propertyClass = attributes->objectClass; if (propertyClass ! = nil) {// ** This method is implemented in detail ** / Then remove the type conversion NSValueTransformer object transformer = [self transformerForModelPropertiesOfClass: propertyClass]; If (transformer == nil) transformer = [NSValueTransformer mtl_validatingTransformerForClass:propertyClass ?: NSObject.class]; } else {/ / distinguish BOOL type transformer = [self transformerForModelPropertiesOfObjCType: attributes - > type]? : [NSValueTransformer mtl_validatingTransformerForClass:NSValue.class]; } if (transformer ! = nil) result[key] = transformer; } return result; }Copy the code

8. The MTLJSONAdapter – (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error method completes the dictionary conversion to model operation

- (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {// check whether it is implemented classForParsingJSONDictionary: Agreement if ([self modelClass respondsToSelector: @ the selector (classForParsingJSONDictionary:)]) {/ / get the Class Class Class = [self.modelClass classForParsingJSONDictionary:JSONDictionary]; if (class == nil) { if (error ! = NULL) {/ / error handling NSDictionary * the userInfo = @ {NSLocalizedDescriptionKey: NSLocalizedString(@"Could not parse JSON", @""), NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to parse the JSON dictionary.", @"") }; *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorNoClassFound userInfo:userInfo]; } return nil; } if (class ! = self.modelClass) {NSAssert([class conformsToProtocol:@protocol(MTLJSONSerializing)], @"Class %@ returned from +classForParsingJSONDictionary: does not conform to <MTLJSONSerializing>", class); MTLJSONAdapter *otherAdapter = [self JSONAdapterForModelClass:class error:error]; return [otherAdapter modelFromJSONDictionary:JSONDictionary error:error]; } } NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count]; Key for (NSString *propertyKey in [self.modelClass propertyKeys]) {// JSONKeyPath id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey]; if (JSONKeyPaths == nil) continue; id value; // What is the use of the TODO array? if ([JSONKeyPaths isKindOfClass:NSArray.class]) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; for (NSString *keyPath in JSONKeyPaths) { BOOL success; / / * * this method after the article has a detailed interpretation of * * id value = [JSONDictionary mtl_valueForJSONKeyPath: keyPath success: & success error: error]; if (! success) return nil; if (value ! = nil) dictionary[keyPath] = value; } value = dictionary; } else { BOOL success; / / remove JSONKeyPaths corresponding values in the dictionary / / * * this method after the article has a detailed interpretation of * * value = [JSONDictionary mtl_valueForJSONKeyPath: JSONKeyPaths success:&success error:error]; if (! success) return nil; } if (value == nil) continue; @try {// Fetch the value converted NSValueTransformer object, If the object is empty, explain the propertyKey don't need to do value transformation NSValueTransformer * transformer = self valueTransformersByPropertyKey [propertyKey]; if (transformer ! = nil) { // Map NSNull -> nil for the transformer, and then back for the // dictionary we're going to insert into. if ([value isEqual:NSNull.null]) value = nil; / / value transformation operations if ([transformer respondsToSelector: @ the selector (transformedValue: success: error:)]) {/ / callback conversion process id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer; BOOL success = YES; value = [errorHandlingTransformer transformedValue:value success:&success error:error]; if (! success) return nil; ** value = [transformer transformedValue:value]; // Transformer transformedValue:value]; } if (value == nil) value = NSNull.null; } // save the value of JSONKeyPath transformed by NSValueTransformer dictionaryValue[propertyKey] = value; NSLog(@"*** Caught exception % @parsing JSON key path \"% @" from: parsing {// NSLog(@"*** Caught exception % @parsing JSON key path \"% @" from: Parsing {// NSException *ex) {// NSLog(@"*** Caught exception % @parsing JSON key path \"% @" from: %@", ex, JSONKeyPaths, JSONDictionary); }} / / convert the dictionary into the model / / * * this method after the realization of the article has a detailed interpretation model * * id = [self. ModelClass modelWithDictionary: dictionaryValue error: error]; Return [model validate:error]? model : nil; }Copy the code

9.NSDictionary+ keypath – (id)mtl_valueForJSONKeyPath:(NSString *)JSONKeyPath success:(BOOL *)success error:(NSError **)error; The JSONKeyPath () method fetches the corresponding value from the JSON dictionary based on the JSONKeyPath

- (id)mtl_valueForJSONKeyPath:(NSString *)JSONKeyPath success:(BOOL *)success error:(NSError **)error {//TODO this press. What does splitting a string mean? Where do you use it? At the end of the article shows that 2 NSArray * components = [JSONKeyPath componentsSeparatedByString: @ ""]. id result = self; // dictionary for (NSString *component in components) {// Check the result before resolving the key path component to not affect the last value of the path. if (result == nil || result == NSNull.null) break; if (! [the result isKindOfClass: NSDictionary class]) {/ / error handling code} / / JSONKeyPath as key out the values in the JSON dictionary result = the result/component; } if (success ! = NULL) *success = YES; return result; }Copy the code

10. After obtaining the value corresponding to JSONKeyPath, the next step is to convert the value from the NSValueTransformer object returned by the Model value conversion method

MTLValueTransformer is a value conversion object based on block operations, implementing JSON dictionary -> Model conversion. Realize MTLReversibleValueTransformer inversion operation.




Method definition

@implementation MTLValueTransformer // implement MTLValueTransformer conversion operation, by model block concrete implementation conversion operation, - (id)transformedValue:(id)value {NSError *error = nil; BOOL success = YES; return self.forwardBlock(value, &success, &error); } // The model block is used to perform the conversion. Return the converted value - (id)transformedValue:(id)value success:(BOOL *)outerSuccess error:(NSError **)outerError {NSError *error = nil; BOOL success = YES; // transformedValue = self.forwardBlock(value, &success, &error); if (outerSuccess ! = NULL) *outerSuccess = success; if (outerError ! = NULL) *outerError = error; return transformedValue; } @ the end / / reverse transformation model - > a JSON dictionary @ implementation MTLReversibleValueTransformer / / reverse transformation operations, is changed by the block specific implementation model operation, - (id)reverseTransformedValue:(id)value {NSError *error = nil; BOOL success = YES; return self.reverseBlock(value, &success, &error); } // The model block does the reverse conversion, - (id)reverseTransformedValue:(id)value success:(BOOL *)outerSuccess error:(NSError **)outerError {NSError *error = nil; BOOL success = YES; Id transformedValue = self.reverseBlock(value, &success, &error); if (outerSuccess ! = NULL) *outerSuccess = success; if (outerError ! = NULL) *outerError = error; return transformedValue; } @endCopy the code

11. After converting the processed values, the next step is to assign the Model property values obtained from the JSON dictionary to the corresponding Model objects. In the MTLJSONAdapter – (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error The last part of the implementation is used to generate the corresponding Model object.




Code segment

// initialize model with dictionary + (instancetype)modelWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {  return [[self alloc] initWithDictionary:dictionary error:error]; }... // initialize model with dictionary - (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {self = [self init]; if (self == nil) return nil; // Mark this as being autoreleased, because validateValue may return // a new object to be stored in this variable (and we don't want ARC to // double-free or leak the old or new values). __autoreleasing id value = [dictionary objectForKey:key]; if ([value isEqual:NSNull.null]) value = nil; BOOL SUCCESS = MTLValidateAndSetValue(self, key, value, YES, error); if (! success) return nil; } return self; } // Determine if a model attribute can be assigned with KVC, Static BOOL MTLValidateAndSetValue(id obj, NSString *key, id value, BOOL forceUpdate, NSError **error) { __autoreleasing id validatedValue = value; @try {// Developers can call validateValue when they need to verify that they can set a value with KVC: -(BOOL)validate<Key>:error: // If (BOOL)validate<Key>:error: YES if (! [obj validateValue:&validatedValue forKey:key error:error]) return NO; // set the new value // compare the validatedValue returned by obj with the value passed in, If inconsistent with validatedValue value / / forceUpdate to YES then was also set directly to the key value if (forceUpdate | | value! = validatedValue) { [obj setValue:validatedValue forKey:key]; } return YES; } @catch (NSException *ex) { NSLog(@"*** Caught exception setting key \"%@\" : %@", key, ex); }} // {//} - (BOOL)validate:(NSError **)error {for (NSString *key in self.class.propertykeys) {id value = [self valueForKey:key]; // Verify that the model has an attribute that cannot be assigned by KVC, and return NO if there is an attribute that cannot be assigned by KVC. BOOL success = MTLValidateAndSetValue(self, key, value, NO, error); BOOL success = MTLValidateAndSetValue(self, key, value, NO, error); if (! success) return NO; } return YES; }Copy the code

Mantle After the final validation of the Model properties, return the associated Model object. Mantle’s JSON dictionary –> Model process is now complete.

13. Application scenario description of TODO There are two TODO descriptions in this paper, which are all explained here due to limited space. TODO can search keywords to find articles do civilian TODO markers / / TODO [value isKindOfClass: NSArray. Class] judge what is this? Where do you use it?

/ / a JSON dictionary NSDictionary * JSONDict = @ {@ "code" : @ 200, @ "temp" : @ "59.07", @ "temp_max:" @ "63.32"}; @interface TestModel () {// Interface TestModel (); MTLModel<MTLJSONSerializing> @property (nonatomic,assign) NSInteger code; @property (nonatomic,strong) NSString *temp; @property (nonatomic,strong) NSString *temp_max; @end // But there are cases where you might want to put temp and temp_max in a dictionary. @interface TestModel : MTLModel<MTLJSONSerializing> @property (nonatomic,assign) NSInteger code; @property (nonatomic,strong) NSDictionary *temp; @ the end / / temp and temp_max in a dictionary, then JSONKeyPathsByPropertyKey method to do the following @ implementation TestModel + (NSDictionary *)JSONKeyPathsByPropertyKey{ return @{ @"code":@"code", @"temp":[NSArray arrayWithObjects:@"temp", @"temp_max",nil] }; } @endCopy the code

// TODO this press. What does splitting a string mean? Where do you use it? Note 2 at the end of the article

/ / a JSON dictionary NSDictionary * JSONDict = @ {@ "code" : @ 200, @ "weather" : @ {@ "temp" : @ "59.07", @ "temp_max" : @temp_min: @"53.01"}}; // Add a weather attribute to TestModel @interface TestModel: MTLModel<MTLJSONSerializing> @property (nonatomic,assign) NSInteger code; @property (nonatomic,strong) Weather *weather; @end // But in some cases you don't want to create new object attributes for TestModel. Instead, you want to put all JSONKeyPath levels in the JSON dictionary at the first level. @interface TestModel : MTLModel<MTLJSONSerializing> @property (nonatomic,assign) NSInteger code; @property (nonatomic,strong) NSString *temp; @property (nonatomic,strong) NSString *temp_max; @property (nonatomic,strong) NSString *temp_min; @ the end / / JSON dictionary JSONKeyPath at all levels in the first level, then JSONKeyPathsByPropertyKey method need to be the corresponding hierarchy @ implementation TestModel +(NSDictionary *)JSONKeyPathsByPropertyKey{ return @{ @"code":@"code", @"temp":@"weather.temp", @"temp_max":@"weather.temp_max", @"temp_min":@"weather.temp_min", }; } @endCopy the code

conclusion

Mantle, as a classic JSON dictionary <–> model transformation model framework, mainly uses KVC characteristics to assign values to model. Its framework design has many advantages, such as the design of value transformation process. Reading good open source projects not only expands your technical horizons but also increases your ability to control the details of your code. It is hoped that this article can cast a brick to attract jade and add some information to everyone’s understanding of Mantle. If you feel that the article is helpful to you, then give a praise, is to encourage my hard code word writing, ha ha ha ha ha!

reference

Github.com/Mantle/Mant… Southpeak. Making. IO / 2015/01/11 /… Developer.apple.com/library/con… www.jianshu.com/p/f49ddbf8a… www.jianshu.com/p/9f039124e… Github.com/johnno1962/… Nshipster.com/nsvaluetran…