【iOS】JSONModel源碼學習

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;。它支持參數傳遞、…