JSONModel源码学习

  • 前言
  • JSONModel的使用
      • 最基础的使用
      • 转换属性名称
      • 自定义错误
      • 模型嵌套
      • JSONModel的继承
  • 源码实现
    • initWithDictionary
      • init
    • __doesDictionary
    • importDictionary
  • 优点

前言

之前了解过JSONModel的一些使用方法等,但是对于底层实现并不清楚了解,今天来学习一些JSONModel的源码流程,本篇博客进行一个记录。

JSONModel的使用

最基础的使用

先给出一个当我们单纯传入字典的时候,转化成模型类的用法:

@interface Person : JSONModel@property (nonatomic, copy)   NSString *name;
@property (nonatomic, copy)   NSString *sex;
@property (nonatomic, assign) NSInteger age;@end

使用字典来转换为模型:

NSDictionary *dict = @{@"name":@"Jack",@"age":@23,@"gender":@"male",};NSError *error;Person *person = [[Person alloc] initWithDictionary:dict error:&error];
NSLog(@"%@", person);

结果

<Person> [name]: Jack[age]: 23[gender]: male
</Person>

转换属性名称

有时传入的字典中的key发生了变化(比如说接口重构之类的原因)但是模型属性我们并不好改变,这个时候就需要有一个转化功能去修改:

这里举例将gender变为sex,那我们应该怎么操作呢

+ (JSONKeyMapper *)keyMapper {return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"gender":@"sex"}];
}

来看看结果:

在这里插入图片描述

自定义错误

我们可以自定义属于我们自己的错误判断,比如说我们要限制Person信息的age不能小于18,需要在模型的实现文件中:

- (BOOL)validate:(NSError *__autoreleasing *)error {if (![super validate: error])return NO;if (self.age < 18) {*error = [NSError errorWithDomain:@"Too young" code:10 userInfo:nil];NSError* errorLog = *error;NSLog(@"%@", errorLog.domain);return NO;}return YES;
}

结果:

在这里插入图片描述

会打印错误 信息,同时也不会转化模型

模型嵌套

当我给Person类加一个朋友列表的时候,这个时候就是Person类嵌套一个Friend类,然而这种情况应该怎么操作呢:

#import <Foundation/Foundation.h>
#import <JSONModel/JSONModel.h>
@protocol Friend
@end
NS_ASSUME_NONNULL_BEGIN@interface Friend : JSONModel@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;@end@interface Person : JSONModel
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* gender;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSArray<Friend> *friends;//数组,嵌套模型
@endNS_ASSUME_NONNULL_END

看看结果如何:

在这里插入图片描述

这就是几种JSONModel比较典型的使用,下面来看看底层源码的实现。

JSONModel的继承

笔者看源码发现JSONModel需要扫描父类直至JSONModel这个类,但是嵌套的实现使用的是协议,这里记录一下笔者AI到的继承的使用,若有不对还望指正。

JSONModel的继承是通常适用于返回的数据中都有一段公共字段,我们有很多类型数据的时候,他们都有id和created_at,那么我们可以创建一个BaseModel,令所有类型的model直接继承这个类型model,这样我们可以避免每个子类重写。

源码实现

先来一张流程图

在这里插入图片描述

这里大致讲解一下流程:

首先在这个模型类的对象被初始化的时候,遍历自身到所有的父类,获取所有的属性,将其保存到一个字典中去,获取传入字典的所有的key,将这些key同保存的属性进行匹配。若是匹配成功,就进行KVC赋值


JSONModel一共提供了四种初始化的方法,如下:

-(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
-(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
-(instancetype)initWithData:(NSData *)data error:(NSError **)error;

这里我们从-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;这种最经典的方法讲起:

initWithDictionary

先来看看这个方法的源码实现:

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{//检查参数是否为nilif (!dict) {if (err) *err = [JSONModelError errorInputIsNil];return nil;}//参数不是nil,但是也不是字典if (![dict isKindOfClass:[NSDictionary class]]) {if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];return nil;}//初始化self = [self init];if (!self) {//super init didn't succeedif (err) *err = [JSONModelError errorModelIsInvalid];return nil;}//检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {return nil;}//字典的key与模型的属性的映射if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {return nil;}//可以重写[self validate:err]方法并返回No,令用户自定义错误去阻拦model的返回if (![self validate:err]) {return nil;}//model is valid! yay!return self;
}

总结

  • 1-4步中都是对错误的发现和处理
  • 方法5是真正的mapping
  • 方法6是作者自定义错误的方法,若是复合了自定义的错误,即使mapping成功也要返回nil
  • 方法7成功返回模型对象

在开始之前,先来了解一下JSONModel所持有的数据:

static const char * kMapperObjectKey;//自定义的mapper,具体使用方法在上面的例子
static const char * kClassPropertiesKey;//用来保存所有属性信息的NSDictionary
static const char * kClassRequiredPropertyNamesKey;//用来保存所有属性的名称NSSet
static const char * kIndexPropertyNameKey;

KeyMapper的使用

JSONModel 提供了一个叫做 JSONKeyMapper 的工具类,用于在 JSON 数据中查找与类属性名相对应的属性名,以便进行正确的映射。

JSONKeyMapper 是一个可定制的映射器,它提供了两种映射方式:

  • 下划线式(UnderscoreCase)映射:将下划线形式的 JSON 数据中的属性名转换成类属性名(如:foo_bar -> fooBar)。
  • 驼峰式(CamelCase)映射:将驼峰形式的 JSON 数据中的属性名转换成类属性名(如:fooBar -> foo_bar)。
JSONKeyMapper *mapper = [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"propertyOne": @"property_one",@"propertyTwo": @"property_two"
}];
MyModel *model = [[MyModel alloc] initWithDictionary:jsonDict error:nil];

init

我们从第三个方法init开始,看看流程:

-(id)init
{self = [super init];if (self) {//do initial class setup[self __setup__];}return self;
}-(void)__setup__
{//如果第一次实例化,就执行if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {[self __inspectProperties];}//若是存在自定义的mapper,就将其保存在关联对象中,key是KMapperObjectKeyid mapper = [[self class] keyMapper];if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {objc_setAssociatedObject(self.class,&kMapperObjectKey,mapper,OBJC_ASSOCIATION_RETAIN // This is atomic);}
}

这里我们使用!objc_getAssociatedObject(self.class, &kMapperObjectKey) 的时候第一个参数使用self.class,这就是说这里我们获取当前类的关联对象,要是关联对象不存在,就说明当前类还没有被解析过,需要调用__inspectProperties方法进行解析

在第一次实例化的时候,所调用的__inspectProperties也是该框架的核心方法之一:其保存了所有需要赋值的属性,用作在将来与传进来字典进行映射

具体来说,该方法会使用运行时特性获取模型类的属性列表,并为每个属性创建一个 JSONModelProperty 对象,该对象包含属性名、数据类型、对应的 JSON 字段名等信息。然后,这些 JSONModelProperty 对象将存储在一个 NSMutableDictionary 对象中,以属性名作为键,JSONModelProperty 对象作为值。最后,该 NSMutableDictionary 对象将使用 objc_setAssociatedObject 方法与模型类关联起来,以便以后可以方便地访问。

-(void)__inspectProperties
{//    最终保存所有属性的字典,形式为:
//    {
//        age = "@property primitive age (Setters = [])";
//        friends = "@property NSArray* friends (Standard JSON type, Setters = [])";
//        gender = "@property NSString* gender (Standard JSON type, Setters = [])";
//        name = "@property NSString* name (Standard JSON type, Setters = [])";
//    }NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];//获取当前的类名Class class = [self class];NSScanner* scanner = nil;NSString* propertyType = nil;// 循环条件:当class是JSONModel自己的时候会终止while (class != [JSONModel class]) {//JMLog(@"inspecting: %@", NSStringFromClass(class));//所有属性的个数unsigned int propertyCount;//获取属性列表objc_property_t *properties = class_copyPropertyList(class, &propertyCount);///遍历所有的属性for (unsigned int i = 0; i < propertyCount; i++) {//获得属性名称objc_property_t property = properties[i];//获得当前的属性const char *propertyName = property_getName(property);//name(C字符串)  //JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];p.name = @(propertyName);//propertyName:属性名称,例如:name,age,gender//获得属性类型const char *attrs = property_getAttributes(property);NSString* propertyAttributes = @(attrs);// T@\"NSString\",C,N,V_name// Tq,N,V_age// T@\"NSString\",C,N,V_gender// T@"NSArray",&,N,V_friends            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];//说明是只读属性,不做任何操作if ([attributeItems containsObject:@"R"]) {continue; //to next property}//检查出是布尔值if ([propertyAttributes hasPrefix:@"Tc,"]) {p.structName = @"BOOL";//使其变为结构体}            //实例化一个scannerscanner = [NSScanner scannerWithString: propertyAttributes];[scanner scanUpToString:@"T" intoString: nil];[scanner scanString:@"T" intoString:nil];      if ([scanner scanString:@"@\"" intoString: &propertyType]) {                //属性是一个对象[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]intoString:&propertyType];//propertyType -> NSString                p.type = NSClassFromString(propertyType);// p.type = @"NSString"p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound); //判断是否是可变的对象p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型//存在协议(数组,也就是嵌套模型)while ([scanner scanString:@"<" intoString:NULL]) {NSString* protocolName = nil;[scanner scanUpToString:@">" intoString: &protocolName];if ([protocolName isEqualToString:@"Optional"]) {p.isOptional = YES;} else if([protocolName isEqualToString:@"Index"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"p.isIndex = YES;
#pragma GCC diagnostic popobjc_setAssociatedObject(self.class,&kIndexPropertyNameKey,p.name,OBJC_ASSOCIATION_RETAIN // This is atomic);} else if([protocolName isEqualToString:@"Ignore"]) {p = nil;} else {p.protocol = protocolName;}//到最接近的>为止[scanner scanString:@">" intoString:NULL];}}            else if ([scanner scanString:@"{" intoString: &propertyType])                //属性是结构体[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]intoString:&propertyType];p.isStandardJSONType = NO;p.structName = propertyType;}else {//属性是基本类型:Tq,N,V_age[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]intoString:&propertyType];//propertyType:qpropertyType = valueTransformer.primitivesNames[propertyType];              //propertyType:long//基本类型数组if (![allowedPrimitiveTypes containsObject:propertyType]) {//类型不支持@throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]userInfo:nil];}}NSString *nsPropertyName = @(propertyName);            //可选的if([[self class] propertyIsOptional:nsPropertyName]){p.isOptional = YES;}//可忽略的if([[self class] propertyIsIgnored:nsPropertyName]){p = nil;}//集合类Class customClass = [[self class] classForCollectionProperty:nsPropertyName];            if (customClass) {p.protocol = NSStringFromClass(customClass);}//忽略blockif ([propertyType isEqualToString:@"Block"]) {p = nil;}//如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)if (p && ![propertyIndex objectForKey:p.name]) {[propertyIndex setValue:p forKey:p.name];}//setter 和 getterif (p){   //name ->NameNSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];// getterSEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);if ([self respondsToSelector:getter])p.customGetter = getter;// settersp.customSetters = [NSMutableDictionary new];SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);if ([self respondsToSelector:genericSetter])p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];for (Class type in allowedJSONTypes){NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);if (p.customSetters[class])continue;SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);if ([self respondsToSelector:setter])p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];}}}free(properties);//再指向自己的父类,知道等于JSONModel才停止class = [class superclass];}//最后保存所有当前类,JSONModel的所有的父类的属性objc_setAssociatedObject(self.class,&kClassPropertiesKey,[propertyIndex copy],OBJC_ASSOCIATION_RETAIN);//使用关联对象与模型类关联起来
}

这里有几点需要注意:

  • 作者使用一个while函数,获取当前类和当前类的除去JSONModel的所有父类的属性保存在一个字典中。用来和传入的字典进行一个映射关系。
  • 作者使用JSONModelClassProperty类封装了JSONModel的每一个属性,这个类有两个重要的属性:一个是name,这是属性的名称;另一个是type,这是属性的类型
  • 作者将属性分为了以下几个类型:
    • 对象(不含有协议)
    • 对象(含有协议,即模型嵌套)
    • 基本数据类型
    • 结构体

__doesDictionary

-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{//拿到字典中所有的keyNSArray* incomingKeysArray = [dict allKeys];//返回保存所有属性名称的数组(name,age,gender...)NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;//从array中拿到setNSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];//若是用户自定义了mapper,进行一个转换if (keyMapper || globalKeyMapper) {NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];NSString* transformedName = nil;//遍历需要转换的属性列表for (JSONModelClassProperty* property in [self __properties__]) {//被转换成的属性名称 gender(模型内) -> sex(字典内)transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//拿到sex以后,查看传入的字典里是否有sex对应的值@try {value = [dict valueForKeyPath:transformedName];}@catch (NSException *exception) {value = dict[transformedName];}//若是值存在,就将sex添加到传入的keys数组中if (value) {[transformedIncomingKeys addObject: property.name];}}//用映射的键名称覆盖原始传入列表incomingKeys = transformedIncomingKeys;}//查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误。//也就是说模型类里的属性是不能多于传入字典里的key的,例如:if (![requiredProperties isSubsetOfSet:incomingKeys]) {//获取缺失属性的列表(获取多出来的属性)[requiredProperties minusSet:incomingKeys];//并非所有必需的属性都在 in - 输入无效JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];return NO;}//not needed anymoreincomingKeys= nil;requiredProperties= nil;return YES;
}
  • 这个方法就将我们使用过程中重写的keyMapper方法中的属性名称进行了一个转换,按照我们的需求进行一个改变
  • 这里我们可以看到在最后一个if判断中说明,我们传入的字典数据中的key集合不能小于model类的定义的属性集合,也就是说我们model类中定义的属性集合必须要赋值。

importDictionary

终于到了将NSDictionary对象转化为JSONModel对象这一步了,来看看会发生什么:

-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{//遍历保存的所有属性的字典for (JSONModelClassProperty* property in [self __properties__]) {//将属性的名称拿过来,作为key,用这个key来查找传进来的字典里对应的值NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;//用来保存从字典里获取的值id jsonValue;        @try {jsonValue = [dict valueForKeyPath: jsonKeyPath];}@catch (NSException *exception) {jsonValue = dict[jsonKeyPath];}//字典不存在对应的keyif (isNull(jsonValue)) {//如果这个key是可以不存在的if (property.isOptional || !validation) continue;            //如果这个key是必须有的,则返回错误if (err) {NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}        //获取 取到的值的类型Class jsonValueClass = [jsonValue class];BOOL isValueOfAllowedType = NO;//查看是否是本框架兼容的属性类型for (Class allowedType in allowedJSONTypes) {if ( [jsonValueClass isSubclassOfClass: allowedType] ) {isValueOfAllowedType = YES;break;}}        //如果不兼容,则返回NO,mapping失败if (isValueOfAllowedType==NO) {//type not allowedJMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));if (err) {NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}//如果是兼容的类型:if (property) {// 查看是否有自定义setter,并设置if ([self __customSetValue:jsonValue forProperty:property]) {continue;};// 基本类型if (property.type == nil && property.structName==nil) {//kvc赋值if (jsonValue != [self valueForKey:property.name]) {[self setValue:jsonValue forKey: property.name];}continue;}// 如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它if (isNull(jsonValue)) {if ([self valueForKey:property.name] != nil) {[self setValue:nil forKey: property.name];}continue;}// 1. 属性本身是否是jsonmodel类型if ([self __isJSONModelSubClass:property.type]) {//通过自身的转模型方法,获取对应的值JSONModelError* initErr = nil;id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];if (!value) {               //如果该属性不是必须的,则略过if (property.isOptional || !validation) continue;//如果该属性是必须的,则返回错误if((err != nil) && (initErr != nil)){*err = [initErr errorByPrependingKeyPathComponent:property.name];}return NO;}            //当前的属性值为空,则赋值if (![value isEqual:[self valueForKey:property.name]]) {[self setValue:value forKey: property.name];}continue;} else {// 如果不是jsonmodel的类型,则可能是一些普通的类型:NSArray,NSString。。。// 是否是模型嵌套(带有协议)if (property.protocol) {//转化为数组,这个数组就是例子中的friends属性。jsonValue = [self __transform:jsonValue forProperty:property error:err];if (!jsonValue) {if ((err != nil) && (*err == nil)) {NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];}return NO;}}// 对象类型if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {//可变类型if (property.isMutable) {jsonValue = [jsonValue mutableCopy];}//赋值if (![jsonValue isEqual:[self valueForKey:property.name]]) {[self setValue:jsonValue forKey: property.name];}continue;}// 当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:- (NSSet *)NSSetFromNSArray:(NSArray *)array)if ((![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))||//the property is mutableproperty.isMutable||//custom struct propertyproperty.structName) {Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];//JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",(property.structName? property.structName : property.type), //target namesourceClass]; //source nameSEL selector = NSSelectorFromString(selectorName);//查看自定义的转换器是否存在BOOL foundCustomTransformer = NO;if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES;                        } else {//try for hidden custom transformerselectorName = [NSString stringWithFormat:@"__%@",selectorName];selector = NSSelectorFromString(selectorName);if ([valueTransformer respondsToSelector:selector]) {foundCustomTransformer = YES;}}//如果存在自定义转换器,则进行转换if (foundCustomTransformer) {                        IMP imp = [valueTransformer methodForSelector:selector];id (*func)(id, SEL, id) = (void *)imp;jsonValue = func(valueTransformer, selector, jsonValue);if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];                        } else {                       //没有自定义转换器,返回错误NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];*err = [dataErr errorByPrependingKeyPathComponent:property.name];return NO;                        }} else {// 3.4) handle "all other" cases (if any)if (![jsonValue isEqual:[self valueForKey:property.name]])[self setValue:jsonValue forKey:property.name];}}}}return YES;
}

值得注意的是:

  • 作者在最后给属性赋值的时候使用的是kvc的setValue:ForKey:的方法。
  • 作者判断了模型里的属性的类型是否是JSONModel的子类,可见作者的考虑是非常周全的。
  • 整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(JSONModelError),里面支持的错误类型很多。

在设置属性值时,__importDictionary方法还会进行一些类型转换和校验

  • 如果属性是JSONModel类型,则将字典转换为该类型的JSONModel对象,并设置到当前属性中。如果属性是基本数据类型(如int、float等),则将字典中的数值转换为相应的数据类型,并设置到当前属性中。
  • 如果属性是NSDate类型,则将字典中的时间戳转换为NSDate对象,并设置到当前属性中。总之,__importDictionary方法的主要作用是将NSDictionary对象转换为JSONModel对象,并进行一些类型转换和校验。

这里有一点笔者之前了解不够的地方记录一下:

@property (nonatomic, strong) NSString<Optional> *optionalString; 

当我们使用<Optional>这个协议标记的时候,说明这个属性不是必须实现的,当我们没有实现这个属性的时候,其被赋值为nil

优点

  • Runtime动态解析model数据类型
  • keyMapper映射
  • KVC赋值
  • 使用关联对象来避免重复解析相同模型,很妙

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/92722.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92722.shtml
英文地址,请注明出处:http://en.pswp.cn/web/92722.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SmartMediaKit 模块化音视频框架实战指南:场景链路 + 能力矩阵全解析

✳️ 引言&#xff1a;从“内核能力”到“模块体系”的演进 自 2015 年起&#xff0c;大牛直播SDK&#xff08;SmartMediaKit&#xff09;便致力于打造一个可深度嵌入、跨平台兼容、模块自由组合的实时音视频基础能力框架。经过多轮技术迭代与场景打磨&#xff0c;该 SDK 已覆…

【第5话:相机模型1】针孔相机、鱼眼相机模型的介绍及其在自动驾驶中的作用及使用方法

相机模型介绍及相机模型在自动驾驶中的作用及使用方法 相机模型是计算机视觉中的核心概念&#xff0c;用于描述真实世界中的点如何投影到图像平面上。在自动驾驶系统中&#xff0c;相机模型用于环境感知&#xff0c;如物体检测和场景理解。下面我将详细介绍针孔相机模型和鱼眼相…

推荐一款优质的开源博客与内容管理系统

Halo是一款由Java Spring Boot打造的开源博客与内容管理系统&#xff08;CMS&#xff09;&#xff0c;在 GitHub上拥有超过36K Start的活跃开发者社区。它使用GPL‑3.0授权开源&#xff0c;稳定性与可维护性极高。 Halo的设计简洁、注重性能&#xff0c;同时保持高度灵活性&a…

【GPT入门】第43课 使用LlamaFactory微调Llama3

【GPT入门】第43课 使用LlamaFactory微调Llama31.环境准备2. 下载基座模型3.LLaMA-Factory部署与启动4. 重新训练![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e7aa869f8e2c4951a0983f0918e1b638.png)1.环境准备 采购autodl服务器&#xff0c;24G,GPU,型号3090&am…

计算机网络:如何理解目的网络不再是一个完整的分类网络

这一理解主要源于无分类域间路由&#xff08;CIDR&#xff09;技术的广泛应用&#xff0c;它打破了传统的基于类的IP地址分配方式。具体可从以下方面理解&#xff1a; 传统分类网络的局限性&#xff1a;在早期互联网中&#xff0c;IP地址被分为A、B、C等固定类别&#xff0c;每…

小米开源大模型 MiDashengLM-7B:不仅是“听懂”,更能“理解”声音

目录 前言 一、一枚“重磅炸弹”&#xff1a;开源&#xff0c;意味着一扇大门的敞开 二、揭秘MiDashengLM-7B&#xff1a;它究竟“神”在哪里&#xff1f; 2.1 “超级耳朵” 与 “智慧大脑” 的协作 2.2 突破&#xff1a;从 “听见文字” 到 “理解世界” 2.3 创新训练&a…

mysql出现大量redolog、undolog排查以及解决方案

排查步骤 监控日志增长情况 -- 查看InnoDB状态 SHOW ENGINE INNODB STATUS;-- 查看redo log配置和使用情况 SHOW VARIABLES LIKE innodb_log_file%; SHOW VARIABLES LIKE innodb_log_buffer_size;-- 查看undo log信息 SHOW VARIABLES LIKE innodb_undo%;检查长时间运行的事务 -…

华为网路设备学习-28(BGP协议 三)路由策略

目录&#xff1a; 一、BGP路由汇总1、注&#xff1a;使用network命令注入的BGP不会被自动汇总2、主类网络号计算过程如下&#xff1a;3.示例 开启BGP路由自动汇总bgp100 开启BGP路由自动汇总import-route 直连路由 11.1.1.0 /24对端 为 10.1.12.2 AS 2004.手动配置BGP路…

微信小程序中实现表单数据实时验证的方法

一、实时验证的基本实现思路表单实时时验证通过监听表单元素的输入事件&#xff0c;在用户输入过程中即时对数据进行校验&#xff0c;并并即时反馈验证结果&#xff0c;主要实现步骤包括&#xff1a;为每个表单字段绑定输入事件在事件处理函数中获取当前输入值应用验证规则进行…

openpnp - 顶部相机如果超过6.5米影响通讯质量,可以加USB3.0信号放大器延长线

文章目录openpnp - 顶部相机如果超过6.5米影响通讯质量&#xff0c;可以加USB3.0信号放大器延长线概述备注ENDopenpnp - 顶部相机如果超过6.5米影响通讯质量&#xff0c;可以加USB3.0信号放大器延长线 概述 手头有1080x720x60FPS的摄像头模组备件&#xff0c;换上后&#xff…

【驱动】RK3576-Debian系统使用ping报错:socket operation not permitted

1、问题描述 在RK3576-Debian系统中,连接了Wifi后,测试网络通断时,报错: ping www.csdn.net ping: socktype: SOCK_RAW ping: socket: Operation not permitted ping: => missing cap_net_raw+p capability or setuid?2、原因分析 2.1 分析打印日志 socktype: SOCK…

opencv:图像轮廓检测与轮廓近似(附代码)

目录 图像轮廓 cv2.findContours(img, mode, method) 绘制轮廓 轮廓特征与近似 轮廓特征 轮廓近似 轮廓近似原理 opencv 实现轮廓近似 轮廓外接矩形 轮廓外接圆 图像轮廓 cv2.findContours(img, mode, method) mode:轮廓检索模式&#xff08;通常使用第四个模式&am…

mtrace定位内存泄漏问题(仅限 GNU glibc 的 Linux)

一、mtrace原理 函数拦截机制&#xff1a;mtrace 利用 glibc 的内部机制&#xff0c;对 malloc() / calloc() / realloc() / free() 等内存函数进行 hook&#xff0c;记录每一次分配和释放行为。日志记录&#xff1a;记录会写入 MALLOC_TRACE 环境变量指定的日志文件中&#xf…

高校合作 | 世冠科技联合普华、北邮项目入选教育部第二批工程案例

近日&#xff0c;教育部学位与研究生教育发展中心正式公布第二批工程案例立项名单。由北京世冠金洋科技发展有限公司牵头&#xff0c;联合普华基础软件、北京邮电大学共同申报的"基于国产软件栈的汽车嵌入式软件开发工程案例"成功入选。该项目由北京邮电大学修佳鹏副…

TOMCAT笔记

一、前置知识&#xff1a;Web 技术演进 C/S vs B/S – C/S&#xff1a;Socket 编程&#xff0c;QQ、迅雷等&#xff0c;通信层 TCP/UDP&#xff0c;协议私有。 – B/S&#xff1a;浏览器 HTTP&#xff0c;文本协议跨网络。 动态网页诞生 早期静态 HTML → 1990 年 HTTP 浏览…

上海一家机器人IPO核心零部件依赖外购, 募投计划频繁修改引疑

作者&#xff1a;Eric来源&#xff1a;IPO魔女8月8日&#xff0c;节卡机器人股份有限公司&#xff08;简称“节卡股份”&#xff09;将接受上交所科创板IPO上会审核。公司保荐机构为国泰海通证券股份有限公司&#xff0c;拟募集资金为6.76亿元。报告期内&#xff0c;节卡股份营…

Linux810 shell 条件判断 文件工具 ifelse

变量 条件判断 -ne 不等 $(id -u) -eq [codesamba ~]$ [ $(id -u) -ne 0 ] && echo "the user is not admin" the user is not admin [codesamba ~]$ [ $(id -u) -eq 0] && echo "yes admin" || echo "no not " -bash: [: 缺少 …

ChatGPT 5的编程能力宣传言过其实

2025年的8月7日&#xff0c;OpenAI 正式向全球揭开了GPT-5的神秘面纱&#xff0c;瞬间在 AI 领域乃至整个科技圈引发了轩然大波。OpenAI对GPT-5的宣传可谓不遗余力&#xff0c;将其描绘成一款具有颠覆性变革的 AI 产品&#xff0c;尤其在编程能力方面&#xff0c;给出了诸多令人…

从MySQL到大数据平台:基于Spark的离线分析实战指南

引言在当今数据驱动的商业环境中&#xff0c;企业业务数据通常存储在MySQL等关系型数据库中&#xff0c;但当数据量增长到千万级甚至更高时&#xff0c;直接在MySQL中进行复杂分析会导致性能瓶颈。本文将详细介绍如何将MySQL业务数据迁移到大数据平台&#xff0c;并通过Spark等…

Mysql笔记-存储过程与存储函数

1. 存储过程(Stored Procedure) 1.1 概述 1.1.1 定义&#xff1a; 存储过程是一组预编译的 SQL 语句和控制流语句&#xff08;如条件判断、循环&#xff09;的集合&#xff0c;​无返回值​&#xff08;但可通过 OUT/INOUT 参数或结果集返回数据&#xff09;。它支持参数传递、…