From ca2b9f29fcccb70e724e26b6dbeeedd6e4285ec4 Mon Sep 17 00:00:00 2001 From: Frank <472730949@qq.com> Date: Fri, 31 Dec 2021 17:16:10 +0800 Subject: [PATCH] Boost speed for JSON->model conversion process.(#837 #835 #834) --- MJExtension.xcodeproj/project.pbxproj | 22 +- MJExtension/MJEClass.h | 3 +- MJExtension/MJEClass.m | 26 +- MJExtension/MJExtensionConst.h | 2 +- MJExtension/MJExtensionProtocols.h | 19 +- MJExtension/MJFoundation.m | 1 - MJExtension/MJProperty.h | 4 + MJExtension/MJProperty.m | 14 + MJExtension/NSDate+MJExtension.h | 16 + MJExtension/NSDate+MJExtension.m | 26 + MJExtension/NSObject+MJKeyValue.m | 462 ++++++++++++++++-- MJExtension/NSString+MJExtension.h | 11 + MJExtension/NSString+MJExtension.mm | 64 +++ MJExtensionTests/Benchmark.swift | 2 +- MJExtensionTests/DeprecatedAPITests.swift | 9 + .../MJExtensionTests-Bridging-Header.h | 3 + MJExtensionTests/MJExtensionTests.m | 9 +- MJExtensionTests/Model/MJBaseObject.h | 2 +- MJExtensionTests/Model/MJCat.m | 2 +- MJExtensionTests/Model/MJDog.h | 2 +- MJExtensionTests/Model/MJPerson.m | 2 +- MJExtensionTests/Model/MJStatusResult.m | 2 +- .../TestAllConversionCorrectness.xctestplan | 3 +- .../SwiftModel/MJCredential.swift | 3 + MJExtensionTests/ValueTransformingTest.swift | 35 -- MJExtensionTests/ValueTransformingTests.swift | 71 +++ 26 files changed, 703 insertions(+), 112 deletions(-) create mode 100644 MJExtension/NSDate+MJExtension.h create mode 100644 MJExtension/NSDate+MJExtension.m create mode 100644 MJExtensionTests/DeprecatedAPITests.swift delete mode 100644 MJExtensionTests/ValueTransformingTest.swift create mode 100644 MJExtensionTests/ValueTransformingTests.swift diff --git a/MJExtension.xcodeproj/project.pbxproj b/MJExtension.xcodeproj/project.pbxproj index 144399a4..66045d07 100644 --- a/MJExtension.xcodeproj/project.pbxproj +++ b/MJExtension.xcodeproj/project.pbxproj @@ -21,7 +21,10 @@ 0130EE80233C56D8008D2386 /* MJFrenchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130EE7F233C56D8008D2386 /* MJFrenchUser.m */; }; 0179886C24EFA460007F7FBC /* MJTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886B24EFA460007F7FBC /* MJTester.swift */; }; 0179887024EFA58B007F7FBC /* SwiftModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */; }; - 01BC91E2277418DF004E5265 /* ValueTransformingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC91E1277418DF004E5265 /* ValueTransformingTest.swift */; }; + 01BB00FD277EC1FF002EF5B3 /* DeprecatedAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BB00FC277EC1FF002EF5B3 /* DeprecatedAPITests.swift */; }; + 01BB0100277EE8DC002EF5B3 /* NSDate+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 01BB00FE277EE8DB002EF5B3 /* NSDate+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01BB0101277EE8DC002EF5B3 /* NSDate+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 01BB00FF277EE8DB002EF5B3 /* NSDate+MJExtension.m */; }; + 01BC91E2277418DF004E5265 /* ValueTransformingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC91E1277418DF004E5265 /* ValueTransformingTests.swift */; }; 01BC91E427741956004E5265 /* MJCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC91E327741956004E5265 /* MJCredential.swift */; }; 01BC91E627741D8C004E5265 /* MJArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC91E527741D8C004E5265 /* MJArticle.swift */; }; 01BC921327747C06004E5265 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC920B27747C05004E5265 /* Benchmark.swift */; }; @@ -95,7 +98,10 @@ 0179886A24EFA460007F7FBC /* MJExtensionTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MJExtensionTests-Bridging-Header.h"; sourceTree = ""; }; 0179886B24EFA460007F7FBC /* MJTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MJTester.swift; sourceTree = ""; }; 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftModelTests.swift; sourceTree = ""; }; - 01BC91E1277418DF004E5265 /* ValueTransformingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueTransformingTest.swift; sourceTree = ""; }; + 01BB00FC277EC1FF002EF5B3 /* DeprecatedAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPITests.swift; sourceTree = ""; }; + 01BB00FE277EE8DB002EF5B3 /* NSDate+MJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+MJExtension.h"; sourceTree = ""; }; + 01BB00FF277EE8DB002EF5B3 /* NSDate+MJExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDate+MJExtension.m"; sourceTree = ""; }; + 01BC91E1277418DF004E5265 /* ValueTransformingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueTransformingTests.swift; sourceTree = ""; }; 01BC91E327741956004E5265 /* MJCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MJCredential.swift; sourceTree = ""; }; 01BC91E527741D8C004E5265 /* MJArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MJArticle.swift; sourceTree = ""; }; 01BC920B27747C05004E5265 /* Benchmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = ""; }; @@ -268,6 +274,8 @@ 2D2DBA062317DB32005A689E /* NSObject+MJKeyValue.m */, 2D2DBA142317DB33005A689E /* NSString+MJExtension.h */, 2D2DBA0B2317DB32005A689E /* NSString+MJExtension.mm */, + 01BB00FE277EE8DB002EF5B3 /* NSDate+MJExtension.h */, + 01BB00FF277EE8DB002EF5B3 /* NSDate+MJExtension.m */, 2D2DB9F62317DA64005A689E /* MJExtension.h */, 2D2DB9F72317DA64005A689E /* Info.plist */, ); @@ -283,11 +291,12 @@ 0107507526E88DA600AAEA10 /* CoreDataModel */, 2D2DBA5F2317DBDF005A689E /* Model */, 2D2DBA572317DBB9005A689E /* MJExtensionTests.m */, - 01BC91E1277418DF004E5265 /* ValueTransformingTest.swift */, + 01BC91E1277418DF004E5265 /* ValueTransformingTests.swift */, 010DF01D2761D90A0007EEF0 /* SpecialPropertyTypeTests.swift */, 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */, 01052EAC25F872D00049EC6F /* MultiThreadTests.swift */, 0107507D26E890C100AAEA10 /* CoreDataTests.swift */, + 01BB00FC277EC1FF002EF5B3 /* DeprecatedAPITests.swift */, 01BC920B27747C05004E5265 /* Benchmark.swift */, 2D2DBA592317DBB9005A689E /* Info.plist */, 2D2DBA872317DCCF005A689E /* PrefixHeader.pch */, @@ -345,6 +354,7 @@ 2D2DBA262317DB33005A689E /* MJProperty.h in Headers */, 2D2DBA252317DB33005A689E /* MJPropertyKey.h in Headers */, 01F5515E2757144500518218 /* MJEClass.h in Headers */, + 01BB0100277EE8DC002EF5B3 /* NSDate+MJExtension.h in Headers */, 2D2DB9F82317DA64005A689E /* MJExtension.h in Headers */, 2D2DBA222317DB33005A689E /* MJFoundation.h in Headers */, ); @@ -538,6 +548,7 @@ 2D2DBA1A2317DB33005A689E /* NSObject+MJKeyValue.m in Sources */, 2D2DBA192317DB33005A689E /* MJPropertyKey.m in Sources */, 2D2DBA202317DB33005A689E /* MJFoundation.m in Sources */, + 01BB0101277EE8DC002EF5B3 /* NSDate+MJExtension.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -560,12 +571,13 @@ 2D2DBA852317DBE0005A689E /* MJPerson.m in Sources */, 01BC921327747C06004E5265 /* Benchmark.swift in Sources */, 0107507C26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift in Sources */, - 01BC91E2277418DF004E5265 /* ValueTransformingTest.swift in Sources */, + 01BC91E2277418DF004E5265 /* ValueTransformingTests.swift in Sources */, 2D2DBA7B2317DBE0005A689E /* MJBox.m in Sources */, 2D2DBA582317DBB9005A689E /* MJExtensionTests.m in Sources */, 010DF01E2761D90A0007EEF0 /* SpecialPropertyTypeTests.swift in Sources */, 2D2DBA7E2317DBE0005A689E /* MJBag.m in Sources */, 2D2DBA7F2317DBE0005A689E /* MJBaseObject.m in Sources */, + 01BB00FD277EC1FF002EF5B3 /* DeprecatedAPITests.swift in Sources */, 2D2DBA7D2317DBE0005A689E /* MJBook.m in Sources */, 0107507E26E890C100AAEA10 /* CoreDataTests.swift in Sources */, 0107507826E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld in Sources */, @@ -682,6 +694,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -742,6 +755,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; diff --git a/MJExtension/MJEClass.h b/MJExtension/MJEClass.h index 7f1f0ad8..cbaa870d 100644 --- a/MJExtension/MJEClass.h +++ b/MJExtension/MJEClass.h @@ -21,12 +21,13 @@ NS_ASSUME_NONNULL_BEGIN BOOL _hasOld2NewModifier; BOOL _hasDictionary2ObjectModifier; BOOL _hasObject2DictionaryModifier; - BOOL _hasClassModifier; BOOL _needsUpdate; /// = _allProperties.count NSInteger _propertiesCount; NSLocale * _Nullable _locale; + NSNumberFormatter * _Nullable _numberFormatter; + NSDateFormatter * _Nullable _dateFormatter; } - (void)setNeedsUpdate; diff --git a/MJExtension/MJEClass.m b/MJExtension/MJEClass.m index 5faed73e..1e08ab60 100644 --- a/MJExtension/MJEClass.m +++ b/MJExtension/MJEClass.m @@ -81,8 +81,14 @@ - (instancetype)initWithClass:(Class)cls { // get generic classes MJEAddSelectorResult2Dictionary(currentClass, - @selector(mj_objectClassInCollection), + @selector(mj_classInfoInCollection), genericClasses); + // Deprecated API compatible + if (![currentClass respondsToSelector:@selector(mj_classInfoInCollection)]) { + MJEAddSelectorResult2Dictionary(currentClass, + @selector(mj_objectClassInArray), + genericClasses); + } // get replaced keys MJEAddSelectorResult2Dictionary(currentClass, @@ -113,10 +119,17 @@ - (instancetype)initWithClass:(Class)cls { if ([cls respondsToSelector:@selector(mj_locale)]) { _locale = [(Class)cls mj_locale]; + } else if ([cls respondsToSelector:@selector(mj_numberLocale)]) { // Deprecated API compatible + _locale = [(Class)cls mj_numberLocale]; + } + if (_locale) { + _numberFormatter = [NSNumberFormatter new]; + _numberFormatter.locale = _locale; + } + if ([cls respondsToSelector:@selector(mj_dateFormatter)]) { + _dateFormatter = [(Class)cls mj_dateFormatter]; } _hasOld2NewModifier = [cls instancesRespondToSelector:@selector(mj_newValueFromOldValue:property:)]; - // TODO: 4.0.0 new feature -// _hasClassModifier = [ _hasDictionary2ObjectModifier = [cls instancesRespondToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]; _hasObject2DictionaryModifier = [cls instancesRespondToSelector:@selector(mj_objectDidConvertToKeyValues:)]; @@ -210,6 +223,13 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList } property.classInCollection = clazz; + // check the ability to change class. + if (clazz) { // generic + property->_hasClassModifier = [clazz respondsToSelector:@selector(mj_modifiedClassForDictionary:)]; + } else if (property.typeClass && property->_basicObjectType == MJEBasicTypeUndefined) { // common class (base class) + property->_hasClassModifier = [property.typeClass respondsToSelector:@selector(mj_modifiedClassForDictionary:)]; + } + [allProperties addObject:property]; } diff --git a/MJExtension/MJExtensionConst.h b/MJExtension/MJExtensionConst.h index 28cfa498..d40ceed1 100644 --- a/MJExtension/MJExtensionConst.h +++ b/MJExtension/MJExtensionConst.h @@ -29,7 +29,7 @@ dispatch_once(&mje_onceTokenSemaphore, ^{ \ }); // 过期 -#define MJExtensionDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) +#define MJE_API_Deprecated(instead) API_DEPRECATED(instead, ios(2.0, 4.0)) // 构建错误 #define MJExtensionBuildError(clazz, msg) \ diff --git a/MJExtension/MJExtensionProtocols.h b/MJExtension/MJExtensionProtocols.h index 777a7718..32ab9043 100644 --- a/MJExtension/MJExtensionProtocols.h +++ b/MJExtension/MJExtensionProtocols.h @@ -64,11 +64,26 @@ /// @discussion - `key` is collection property name /// /// - `value` is the class or class string for recongnizing in JSON. -+ (NSDictionary *)mj_objectClassInCollection; ++ (NSDictionary *)mj_classInfoInCollection; ++ (NSDictionary *)mj_objectClassInArray MJE_API_Deprecated("Use +mj_classInfoCollection instead."); -/// Used in number formatter that covert a string to a number. ++ (Class)mj_modifiedClassForDictionary:(NSDictionary *)dictionary; + +/// Used in number formatter that coverts a string to a number. /// @discussion Normally "100,000" = 100000. But "100,000" = 100 in France. + (NSLocale *)mj_locale; ++ (NSLocale *)mj_numberLocale MJE_API_Deprecated("Use +mj_locale instead."); + +/// Used in date formatter that converts a string to a date. +/// @discussion Following formatters have already been existed by default. +/// @code +/// "yyyy-MM-dd" +/// "yyyy-MM-dd'T'HH:mm:ss" +/// "yyyy-MM-dd'T'HH:mm:ss.SSS" +/// "yyyy-MM-dd'T'HH:mm:ssZ" +/// "yyyy-MM-dd'T'HH:mm:ss.SSSZ" +/// @endcode ++ (NSDateFormatter *)mj_dateFormatter; /// Inherits configurations from super class or not. /// If not configurate, default value is YES. diff --git a/MJExtension/MJFoundation.m b/MJExtension/MJFoundation.m index f4aaeb92..f7f10d86 100644 --- a/MJExtension/MJFoundation.m +++ b/MJExtension/MJFoundation.m @@ -26,7 +26,6 @@ + (BOOL)isClassFromFoundation:(Class)c [NSDate class], [NSValue class], [NSData class], - [NSError class], [NSArray class], [NSDictionary class], [NSString class], diff --git a/MJExtension/MJProperty.h b/MJExtension/MJProperty.h index 4b01610b..e75ea7fd 100644 --- a/MJExtension/MJProperty.h +++ b/MJExtension/MJProperty.h @@ -77,6 +77,8 @@ NS_ASSUME_NONNULL_BEGIN @package /// has old to new value modifying method in Class containing. BOOL _hasValueModifier; + /// has the ability to change generic class or common class(base class) based on value type. + BOOL _hasClassModifier; /// If mappedMultiKeys exist, this value would be true BOOL _isMultiMapping; /// For multiple keys, keypath, subkeys mapping. @@ -118,6 +120,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)setValue:(id)value forObject:(id)object; - (id)valueForObject:(id)object; +- (id)valueInDictionary:(NSDictionary *)dictionary; + /// Initializer by a objc_property_t struct - (instancetype)initWithProperty:(objc_property_t)property inClass:(Class)cls; diff --git a/MJExtension/MJProperty.m b/MJExtension/MJProperty.m index bdf81fee..58d9fbcb 100644 --- a/MJExtension/MJProperty.m +++ b/MJExtension/MJProperty.m @@ -211,6 +211,19 @@ - (id)valueForObject:(id)object return value; } +- (id)valueInDictionary:(NSDictionary *)dictionary { + id value; + for (NSArray *propertyKeys in _mappedMultiKeys) { + value = dictionary; + for (MJPropertyKey *key in propertyKeys) { + value = [key valueInObject:value]; + if (!value) break; + } + if (value) return value; + } + return nil; +} + /** * 设置成员变量的值 */ @@ -218,6 +231,7 @@ - (void)setValue:(id)value forObject:(id)object { if (!_isKVCCompliant || value == nil) return; //FIXME: Bottleneck #4: Enhanced [object setValue:value forKey:self.name]; +// mj_msgSendOne(object, _setter, id, value); } /** 对应着字典中的key */ diff --git a/MJExtension/NSDate+MJExtension.h b/MJExtension/NSDate+MJExtension.h new file mode 100644 index 00000000..34366126 --- /dev/null +++ b/MJExtension/NSDate+MJExtension.h @@ -0,0 +1,16 @@ +// +// NSDate+MJExtension.h +// MJExtension +// +// Created by Frank on 2021/12/31. +// Copyright © 2021 MJ Lee. All rights reserved. +// + +#import + +@interface NSDate (MJExtension) + +/// Return date string based on "yyyy-MM-dd'T'HH:mm:ssZ". +- (NSString *)mj_defaultDateString; + +@end diff --git a/MJExtension/NSDate+MJExtension.m b/MJExtension/NSDate+MJExtension.m new file mode 100644 index 00000000..54b296e7 --- /dev/null +++ b/MJExtension/NSDate+MJExtension.m @@ -0,0 +1,26 @@ +// +// NSDate+MJExtension.m +// MJExtension +// +// Created by Frank on 2021/12/31. +// Copyright © 2021 MJ Lee. All rights reserved. +// + +#import "NSDate+MJExtension.h" + +@implementation NSDate (MJExtension) + +- (NSString *)mj_defaultDateString { + static NSDateFormatter *formatter; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [NSDateFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; + }); + NSString *dateString = [formatter stringFromDate:self]; + return dateString; +} + +@end diff --git a/MJExtension/NSObject+MJKeyValue.m b/MJExtension/NSObject+MJKeyValue.m index 76a62bb2..816fc141 100755 --- a/MJExtension/NSObject+MJKeyValue.m +++ b/MJExtension/NSObject+MJKeyValue.m @@ -13,36 +13,78 @@ #import "MJFoundation.h" #import "MJEClass.h" #import "MJExtensionProtocols.h" +#import "NSDate+MJExtension.h" #define mj_selfSend(sel, type, value) mj_msgSendOne(self, sel, type, value) - -@implementation NSDecimalNumber(MJKeyValue) - -- (id)mj_standardValueWithType:(MJEPropertyType)type { - // 由于这里涉及到编译器问题, 暂时保留 Long, 实际上在 64 位系统上, 这 2 个精度范围相同, - // 32 位略有不同, 其余都可使用 Double 进行强转不丢失精度 - switch (type) { - case MJEPropertyTypeInt64: - return @(self.longLongValue); - case MJEPropertyTypeUInt64: - return @(self.unsignedLongLongValue); - case MJEPropertyTypeInt32: - return @(self.longValue); - case MJEPropertyTypeUInt32: - return @(self.unsignedLongValue); - default: - return @(self.doubleValue); - } -} - -@end +#define mj_selfSet(property, type, value) mj_selfSend(property.setter, type, value) +#define mj_selfGet(property, type) mj_msgSendGet(self, property.getter, type) @interface NSObject () @end - @implementation NSObject (MJKeyValue) +// Special dealing method. `value` should be NSString or NSNumber +- (NSNumber *)mj_numberWithValue:(id)value + type:(MJEPropertyType)type + formatter:(NSNumberFormatter *)formatter { + static NSDictionary *boolStrings; + static dispatch_once_t onceToken; + static NSNumberFormatter *defaultFormatter; + dispatch_once(&onceToken, ^{ + boolStrings = @{ + @"TRUE": @(YES), + @"True": @(YES), + @"true": @(YES), + @"YES": @(YES), + @"Yes": @(YES), + @"yes": @(YES), + + @"FALSE": @(NO), + @"False": @(NO), + @"false": @(NO), + @"NO": @(NO), + @"No": @(NO), + @"no": @(NO), + @"NIL": NSNull.null, + @"Nil": NSNull.null, + @"nil": NSNull.null, + @"NULL": NSNull.null, + @"Null": NSNull.null, + @"null": NSNull.null, + @"(NULL)": NSNull.null, + @"(Null)": NSNull.null, + @"(null)": NSNull.null, + @"": NSNull.null, + @"": NSNull.null, + @"": NSNull.null + }; + + defaultFormatter = [NSNumberFormatter new]; + defaultFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; + }); + if (!value || value == NSNull.null) return nil; + NSNumber *number; + if ([value isKindOfClass:NSNumber.class]) number = value; + if ([value isKindOfClass:NSString.class]) { + NSString *string = value; + NSNumber *num = boolStrings[string]; + if (num) { + number = num; + } else if (type == MJEPropertyTypeDouble) { + number = [formatter ?: defaultFormatter numberFromString:string]; + } else if (type != MJEPropertyTypeLongDouble) { + // NSString -> NSDecimalNumber, 使用 DecimalNumber 来转换数字, 避免丢失精度以及溢出 + number = [NSDecimalNumber + decimalNumberWithString:string locale:formatter.locale]; + if (number == NSDecimalNumber.notANumber) { + number = nil; + } + } + } + return number; +} + #pragma mark - 错误 static const char MJErrorKey = '\0'; + (NSError *)mj_error @@ -83,13 +125,11 @@ - (instancetype)mj_setKeyValues:(id)keyValues { return [self mj_setKeyValues:keyValues context:nil]; } -/** - 核心代码: - */ +/** 核心代码: */ - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context { - // 获得JSON对象 - id object = [keyValues mj_JSONObject]; + id object = keyValues; + if (![object isKindOfClass:NSDictionary.class]) object = [object mj_JSONObject]; MJExtensionAssertError([object isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典"); @@ -107,6 +147,308 @@ - (instancetype)mj_setKeyValues:(id)keyValues } - (void)mj_enumerateProperties:(NSArray *)properties + withDictionary:(NSDictionary *)dictionary + classCache:(MJEClass *)classCache + context:(NSManagedObjectContext *)context { + for (MJProperty *property in properties) { + // get value from dictionary + id value = nil; + if (!property->_isMultiMapping) { + value = dictionary[property->_mappedKey]; + } else { + value = [property valueInDictionary:dictionary]; + } + + // Get value through the modifier + if (classCache->_hasOld2NewModifier + && property->_hasValueModifier) { + id newValue = [self mj_newValueFromOldValue:value property:property]; + if (newValue != value) { // 有过滤后的新值 + [property setValue:newValue forObject:self]; + continue; + } + } + + if (!value) continue; + if (value == NSNull.null) { + mj_selfSet(property, id, nil); + continue; + } + // convert as different cases + MJEPropertyType type = property.type; + Class typeClass = property.typeClass; + Class classInCollecion = property.classInCollection; + MJEBasicType basicObjectType = property->_basicObjectType; + + if (property->_isBasicNumber) { + NSNumber *number = [self mj_numberWithValue:value + type:type + formatter:classCache->_numberFormatter]; + switch (type) { + case MJEPropertyTypeBool: { + mj_selfSet(property, BOOL, number.boolValue); + } break; + case MJEPropertyTypeInt8: { + mj_selfSet(property, int8_t, number.charValue); + } break; + case MJEPropertyTypeUInt8: { + mj_selfSet(property, uint8_t, number.unsignedCharValue); + } break; + case MJEPropertyTypeInt16: { + mj_selfSet(property, int16_t, number.shortValue); + } break; + case MJEPropertyTypeUInt16: { + mj_selfSet(property, uint16_t, number.unsignedShortValue); + } break; + case MJEPropertyTypeInt32: { + mj_selfSet(property, int32_t, number.intValue); + } break; + case MJEPropertyTypeUInt32: { + mj_selfSet(property, uint32_t, number.unsignedIntValue); + } break; + case MJEPropertyTypeInt64: { + mj_selfSet(property, int64_t, number.longLongValue); + } break; + case MJEPropertyTypeUInt64: { + mj_selfSet(property, uint64_t, number.unsignedLongLongValue); + } break; + case MJEPropertyTypeFloat: { + mj_selfSet(property, float, number.floatValue); + } break; + case MJEPropertyTypeDouble: { + mj_selfSet(property, double, number.doubleValue); + } break; + case MJEPropertyTypeLongDouble: { + if ([value isKindOfClass:NSString.class]) { + long double num = [value mj_longDoubleValueWithLocale:classCache->_locale]; + mj_selfSet(property, long double, num); + } else { + mj_selfSet(property, long double, (long double)number.doubleValue); + } + } break; + default: break; + } + } else if (basicObjectType) { + switch (basicObjectType) { + case MJEBasicTypeString: + case MJEBasicTypeMutableString:{ + NSString *result; + if ([value isKindOfClass:NSString.class]) { + result = value; + } else if ([value isKindOfClass:NSNumber.class]) { + result = [value stringValue]; + } else if ([value isKindOfClass:NSData.class]) { + result = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]; + } else if ([value isKindOfClass:NSURL.class]) { + result = [value absoluteString]; + } else if ([value isKindOfClass:NSAttributedString.class]) { + result = [value string]; + } + if (basicObjectType == MJEBasicTypeMutableString) { + result = result.mutableCopy; + } + mj_selfSet(property, id, result); + } break; + + case MJEBasicTypeDate:{ + if ([value isKindOfClass:NSDate.class]) { + mj_selfSet(property, id, value); + } else if ([value isKindOfClass:NSString.class]) { + NSDateFormatter *formatter = classCache->_dateFormatter; + NSDate *date = formatter ? [formatter dateFromString:value] : [value mj_date]; + mj_selfSet(property, id, date); + } + } break; + + case MJEBasicTypeURL:{ + if ([value isKindOfClass:NSURL.class]) { + mj_selfSet(property, id, value); + } else if ([value isKindOfClass:NSString.class]) { + mj_selfSet(property, id, [value mj_url]); + } + } break; + case MJEBasicTypeValue: { + if ([value isKindOfClass:NSValue.class]) { + mj_selfSet(property, id, value); + } + } break; + case MJEBasicTypeNumber: { + NSNumber *num = [self mj_numberWithValue:value + type:type + formatter:classCache->_numberFormatter]; + mj_selfSet(property, id, num); + } break; + case MJEBasicTypeDecimalNumber: { + if ([value isKindOfClass:NSDecimalNumber.class]) { + mj_selfSet(property, id, value); + } else if ([value isKindOfClass:NSNumber.class]) { + NSDecimalNumber *decimalNum = [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]]; + mj_selfSet(property, id, decimalNum); + } else if ([value isKindOfClass:NSString.class]) { + NSDecimalNumber *decimalNum = [NSDecimalNumber decimalNumberWithString:value locale:classCache->_locale]; + if (decimalNum == NSDecimalNumber.notANumber) { + decimalNum = nil; + } + mj_selfSet(property, id, decimalNum); + } + } break; + + case MJEBasicTypeData: + case MJEBasicTypeMutableData:{ + NSData *result; + if ([value isKindOfClass:NSData.class]) { + result = value; + } else if ([value isKindOfClass:NSString.class]) { + result = [value dataUsingEncoding:NSUTF8StringEncoding]; + } + if (basicObjectType == MJEBasicTypeMutableData) { + result = result.mutableCopy; + } + mj_selfSet(property, id, result); + } break; + + case MJEBasicTypeArray: + case MJEBasicTypeMutableArray:{ + NSArray *result; + if ([value isKindOfClass:NSArray.class]) result = value; + else if ([value isKindOfClass:NSSet.class]) result = [value allObjects]; + // generic + if (classInCollecion) { + NSMutableArray *objects = [NSMutableArray new]; + // handle URL array + if (classInCollecion == NSURL.class) { + for (id element in result) { + if ([element isKindOfClass:NSString.class]) [objects addObject:[element mj_url]]; + } + } else { + for (id element in result) { + if ([element isKindOfClass:NSDictionary.class]) { + Class cls = classInCollecion; + if (property->_hasClassModifier) { + cls = [cls mj_modifiedClassForDictionary:element]; + if (!cls) cls = classInCollecion; + } + id object = [cls mj_objectWithKeyValues:element context:context]; + if (object) [objects addObject:object]; + } else if ([element isKindOfClass:classInCollecion]) { + [objects addObject:element]; + } + } + } + mj_selfSet(property, id, objects); + continue; + } + if (basicObjectType == MJEBasicTypeMutableArray) { + result = result.mutableCopy; + } + mj_selfSet(property, id, result); + } break; + + case MJEBasicTypeDictionary: + case MJEBasicTypeMutableDictionary:{ + if (![value isKindOfClass:NSDictionary.class]) continue; + NSDictionary *result = value; + // generic + if (classInCollecion) { + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:result.count]; + [result enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if ([obj isKindOfClass:[NSDictionary class]]) { + Class cls = classInCollecion; + if (property->_hasClassModifier) { + cls = [cls mj_modifiedClassForDictionary:obj]; + if (!cls) cls = classInCollecion; + } + id object = [cls mj_objectWithKeyValues:obj context:context]; + if (object) dict[key] = object; + } + }]; + mj_selfSet(property, id, dict); + continue; + } + if (basicObjectType == MJEBasicTypeMutableDictionary) { + result = result.mutableCopy; + } + mj_selfSet(property, id, result); + } break; + + case MJEBasicTypeSet: + case MJEBasicTypeMutableSet:{ + NSSet *result; + if ([value isKindOfClass:NSArray.class]) result = [NSSet setWithArray:value]; + else if ([value isKindOfClass:NSSet.class]) result = value; + // generic + if (classInCollecion) { + NSMutableSet *set = [NSMutableSet setWithCapacity:result.count]; + for (id element in result) { + if ([element isKindOfClass:classInCollecion]) { + [set addObject:element]; + } else if ([element isKindOfClass:NSDictionary.class]) { + Class cls = classInCollecion; + if (property->_hasClassModifier) { + cls = [cls mj_modifiedClassForDictionary:element]; + if (!cls) cls = classInCollecion; + } + id object = [cls mj_objectWithKeyValues:element context:context]; + if (object) [set addObject:object]; + } + } + mj_selfSet(property, id, set); + continue; + } + if (basicObjectType == MJEBasicTypeMutableSet) { + result = result.mutableCopy; + } + mj_selfSet(property, id, result); + } break; + default: break; + } + } else { + switch (type) { + case MJEPropertyTypeObject: { + if ([value isKindOfClass:typeClass] || !typeClass) { + mj_selfSet(property, id, value); + } else if ([value isKindOfClass:NSDictionary.class]) { + NSObject *subObject = mj_selfGet(property, id); + if (subObject) { + [subObject mj_setKeyValues:value context:context]; + } else { + Class cls = typeClass; + if (property->_hasClassModifier) { + cls = [cls mj_modifiedClassForDictionary:value]; + if (!cls) cls = property.classInCollection; + } + subObject = [cls mj_objectWithKeyValues:value + context:context]; + mj_selfSet(property, id, subObject); + } + } + } break; + case MJEPropertyTypeClass: { + Class cls = nil; + if ([value isKindOfClass:NSString.class]) { + cls = NSClassFromString(value); + if (cls) mj_selfSet(property, Class, cls); + } else { + cls = object_getClass(value); + if (cls && class_isMetaClass(cls)) { + mj_selfSet(property, Class, value); + } + } + } break; + case MJEPropertyTypeSEL: { + if ([value isKindOfClass:NSString.class]) { + SEL selector = NSSelectorFromString(value); + if (selector) mj_selfSet(property, SEL, selector); + } + } break; + default: + break; + } + } + } +} + +- (void)mj_slowEnumerateProperties:(NSArray *)properties withDictionary:(NSDictionary *)dictionary classCache:(MJEClass *)classCache context:(NSManagedObjectContext *)context { @@ -117,13 +459,7 @@ - (void)mj_enumerateProperties:(NSArray *)properties if (!property->_isMultiMapping) { value = dictionary[property->_mappedKey]; } else { - for (NSArray *propertyKeys in property->_mappedMultiKeys) { - value = dictionary; - for (MJPropertyKey *propertyKey in propertyKeys) { - value = [propertyKey valueInObject:value]; - } - if (value) break; - } + value = [property valueInDictionary:dictionary]; } if (classCache->_hasOld2NewModifier @@ -131,12 +467,17 @@ - (void)mj_enumerateProperties:(NSArray *)properties id newValue = [self mj_newValueFromOldValue:value property:property]; if (newValue != value) { // 有过滤后的新值 [property setValue:newValue forObject:self]; - return; + continue; } } // 如果没有值,就直接返回 - if (!value || value == NSNull.null) return; + if (!value) continue; + if (value == NSNull.null) { + mj_selfSet(property, id, nil); + continue; + } + // 2.复杂处理 MJEPropertyType type = property.type; Class propertyClass = property.typeClass; @@ -183,7 +524,11 @@ - (void)mj_enumerateProperties:(NSArray *)properties } else if (type == MJEPropertyTypeLongDouble) { long double num = [value mj_longDoubleValueWithLocale:classCache->_locale]; mj_selfSend(property.setter, long double, num); - return; + continue; + } else if (type == MJEPropertyTypeDouble) { + double num = ((NSString *)value).doubleValue; + mj_selfSend(property.setter, double, num); + continue; } else if (property->_basicObjectType == MJEBasicTypeData || property->_basicObjectType == MJEBasicTypeMutableData) { value = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding].mutableCopy; } else if (property.isNumber) { @@ -198,7 +543,18 @@ - (void)mj_enumerateProperties:(NSArray *)properties if (decimalValue == NSDecimalNumber.notANumber) { value = @(0); } else if (propertyClass != [NSDecimalNumber class]) { - value = [decimalValue mj_standardValueWithType:type]; + switch (type) { + case MJEPropertyTypeInt64: + value = @(decimalValue.longLongValue); + case MJEPropertyTypeUInt64: + value = @(decimalValue.unsignedLongLongValue); + case MJEPropertyTypeInt32: + value = @(decimalValue.longValue); + case MJEPropertyTypeUInt32: + value = @(decimalValue.unsignedLongValue); + default: + value = @(decimalValue.doubleValue); + } } else { value = decimalValue; } @@ -231,7 +587,7 @@ - (void)mj_enumerateProperties:(NSArray *)properties // long double 是不支持 KVC 的 if (property.type == MJEPropertyTypeLongDouble) { mj_selfSend(property.setter, long double, ((NSNumber *)value).doubleValue); - return; + continue; } else { //FIXME: Bottleneck #4: Do not call method [property setValue:value forObject:self]; @@ -255,12 +611,7 @@ + (instancetype)mj_objectWithKeyValues:(id)keyValues return [self mj_objectWithKeyValues:keyValues context:nil]; } -+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context -{ - // 获得JSON对象 - keyValues = [keyValues mj_JSONObject]; - MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典"); - ++ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context { if ([self isSubclassOfClass:[NSManagedObject class]] && context) { NSString *entityName = [(NSManagedObject *)self entity].name; return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context]; @@ -290,23 +641,25 @@ + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context { + id objects = keyValuesArray; // 如果是JSON字符串 - keyValuesArray = [keyValuesArray mj_JSONObject]; + if (![keyValuesArray isKindOfClass:NSArray.class]) { + keyValuesArray = [keyValuesArray mj_JSONObject]; + } // 1.判断真实性 MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组"); // 如果数组里面放的是NSString、NSNumber等数据 if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray]; - // 2.创建数组 NSMutableArray *modelArray = [NSMutableArray array]; // 3.遍历 for (NSDictionary *keyValues in keyValuesArray) { - if ([keyValues isKindOfClass:[NSArray class]]){ + if ([keyValues isKindOfClass:NSArray.class]){ [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]]; - } else { + } else if ([keyValues isKindOfClass:NSDictionary.class]) { id model = [self mj_objectWithKeyValues:keyValues context:context]; if (model) [modelArray addObject:model]; } @@ -357,7 +710,7 @@ - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArr id keyValues = [NSMutableDictionary dictionary]; - MJEClass *mjeClass = [MJEClass cachedClass:self.class ]; + MJEClass *mjeClass = [MJEClass cachedClass:self.class]; NSArray *allProperties = mjeClass->_allProperties; for (MJProperty *property in allProperties) { @@ -372,15 +725,18 @@ - (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArr // 2.如果是模型属性 Class propertyClass = property.typeClass; - if (property->_basicObjectType == MJEBasicTypeUndefined && propertyClass) { + MJEBasicType basicObjectType = property->_basicObjectType; + if (basicObjectType == MJEBasicTypeUndefined && propertyClass) { value = [value mj_keyValues]; } else if ([value isKindOfClass:[NSArray class]]) { // 3.处理数组里面有模型的情况 value = [NSObject mj_keyValuesArrayWithObjectArray:value]; - } else if (property->_basicObjectType == MJEBasicTypeURL) { + } else if (basicObjectType == MJEBasicTypeURL) { value = [value absoluteString]; - } else if (property->_basicObjectType == MJEBasicTypeAttributedString || property->_basicObjectType == MJEBasicTypeMutableAttributedString) { + } else if (basicObjectType == MJEBasicTypeAttributedString || property->_basicObjectType == MJEBasicTypeMutableAttributedString) { value = [(NSAttributedString *)value string]; + } else if (basicObjectType == MJEBasicTypeDate) { + value = [value mj_defaultDateString]; } // 4.赋值 diff --git a/MJExtension/NSString+MJExtension.h b/MJExtension/NSString+MJExtension.h index 3fa2ecef..1da626e9 100644 --- a/MJExtension/NSString+MJExtension.h +++ b/MJExtension/NSString+MJExtension.h @@ -38,4 +38,15 @@ /// Use `strtold_l` method to convert the string. /// @param locale maybe Franch number need this. - (long double)mj_longDoubleValueWithLocale:(NSLocale *)locale; + +/// Convert `String` to `Date` +/// @discussion Following formatters are recognized. +/// @code +/// "yyyy-MM-dd" +/// "yyyy-MM-dd'T'HH:mm:ss" +/// "yyyy-MM-dd'T'HH:mm:ss.SSS" +/// "yyyy-MM-dd'T'HH:mm:ssZ" +/// "yyyy-MM-dd'T'HH:mm:ss.SSSZ" +/// @endcode +- (NSDate *)mj_date; @end diff --git a/MJExtension/NSString+MJExtension.mm b/MJExtension/NSString+MJExtension.mm index 34c1e23b..de4f2b11 100644 --- a/MJExtension/NSString+MJExtension.mm +++ b/MJExtension/NSString+MJExtension.mm @@ -90,4 +90,68 @@ - (long double)mj_longDoubleValueWithLocale:(NSLocale *)locale { - (long double)mj_longDoubleValue { return [self mj_longDoubleValueWithLocale:nil]; } + +- (NSDate *)mj_date { + static NSArray *formatters; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableArray *_formatters = [NSMutableArray arrayWithCapacity:7]; + // Reference: https://developer.apple.com/library/archive/qa/qa1480/ + NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + + NSArray *strings = @[@"yyyy-MM-dd", + @"yyyy-MM-dd'T'HH:mm:ss", + @"yyyy-MM-dd'T'HH:mm:ss.SSS", + @"yyyy-MM-dd'T'HH:mm:ssZ", + @"yyyy-MM-dd'T'HH:mm:ss.SSSZ" + ]; + for (NSString *formatterString in strings) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + formatter.locale = locale; + formatter.timeZone = timeZone; + formatter.dateFormat = formatterString; + [_formatters addObject:formatter]; + } + + formatters = _formatters.copy; + }); + /// "yyyy-MM-dd" + /// "yyyy-MM-dd'T'HH:mm:ss" + /// "yyyy-MM-dd'T'HH:mm:ss.SSS" + /// "yyyy-MM-dd'T'HH:mm:ssZ" + /// "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + typedef NS_ENUM(NSUInteger, FormatterString) { + FormatterStringShort = 10, + FormatterStringLong = 19, + FormatterStringLongMSec = 23, + FormatterStringFull = 20, + FormatterStringFullOrMSec = 24, + FormatterStringFull1 = 25, + FormatterStringFullMSec = 28, + FormatterStringFullMSec1 = 29, + }; + NSDateFormatter *shortFormatter = formatters[0]; + NSDateFormatter *longFormatter = formatters[1]; + NSDateFormatter *longMSecFormatter = formatters[2]; + NSDateFormatter *fullFormatter = formatters[3]; + NSDateFormatter *fullMSecFormatter = formatters[4]; + switch (self.length) { + case FormatterStringShort: + return [shortFormatter dateFromString:self]; + case FormatterStringLong: + return [longFormatter dateFromString:self]; + case FormatterStringLongMSec: + return [longMSecFormatter dateFromString:self]; + case FormatterStringFull: + case FormatterStringFull1: + return [fullFormatter dateFromString:self]; + case FormatterStringFullOrMSec: + return [fullFormatter dateFromString:self] ?: [fullMSecFormatter dateFromString:self]; + case FormatterStringFullMSec: + case FormatterStringFullMSec1: + return [fullMSecFormatter dateFromString:self]; + default: return nil; + } +} @end diff --git a/MJExtensionTests/Benchmark.swift b/MJExtensionTests/Benchmark.swift index 80db9ebd..c15791c3 100644 --- a/MJExtensionTests/Benchmark.swift +++ b/MJExtensionTests/Benchmark.swift @@ -25,7 +25,7 @@ class Benchmark: XCTestCase { } func testPerformanceLargeFile() { - // should less than 4s + // should about 4.x s in my mac (instead of 17s before refactorring) self.measure { let model = LargeModel.mj_objectArray(withKeyValuesArray: jsonObject) print("MJ") diff --git a/MJExtensionTests/DeprecatedAPITests.swift b/MJExtensionTests/DeprecatedAPITests.swift new file mode 100644 index 00000000..a7a29a80 --- /dev/null +++ b/MJExtensionTests/DeprecatedAPITests.swift @@ -0,0 +1,9 @@ +// +// DeprecatedAPITests.swift +// MJExtensionTests +// +// Created by Frank on 2021/12/31. +// Copyright © 2021 MJ Lee. All rights reserved. +// + +import Foundation diff --git a/MJExtensionTests/MJExtensionTests-Bridging-Header.h b/MJExtensionTests/MJExtensionTests-Bridging-Header.h index 4108d1ab..6b376654 100644 --- a/MJExtensionTests/MJExtensionTests-Bridging-Header.h +++ b/MJExtensionTests/MJExtensionTests-Bridging-Header.h @@ -7,3 +7,6 @@ #import "MJUser.h" #import "MJCat.h" #import "MJBag.h" +#import "MJDog.h" +#import "MJBook.h" +#import "MJBox.h" diff --git a/MJExtensionTests/MJExtensionTests.m b/MJExtensionTests/MJExtensionTests.m index 1e7f92ad..a8e7928d 100644 --- a/MJExtensionTests/MJExtensionTests.m +++ b/MJExtensionTests/MJExtensionTests.m @@ -121,11 +121,12 @@ - (void)testJSON2NumberModel { @"gay" : @"", @"speed" : @"120,5", @"identifier" : @"3443623624362", - @"price" : @"20,3", + @"price" : @"20,313422212", @"like" : @"20个", @"collect" : @"收藏5", @"rich" : @"hehe", - @"money_longDouble": @"120,5" + @"money_longDouble": @"120,5", + @"cls": NSNull.null }; // 2.将字典转为MJFrenchUser模型 @@ -137,7 +138,7 @@ - (void)testJSON2NumberModel { XCTAssert(user.gay == NO); XCTAssert(user.speed == 120); XCTAssert(user.identifier == 3443623624362); - XCTAssert(user.price == 20.3); + XCTAssert(user.price == (double)20.313422212); XCTAssert(user.like == 20); XCTAssert(user.collect == 0); XCTAssert(user.rich == NO); @@ -520,7 +521,7 @@ - (void)testReplacedKeyFromPropertyName121 { // 3.检测MJUser模型的属性 XCTAssert([dog.nickName isEqual:@"旺财"]); XCTAssert(dog.salePrice == 10.5); - XCTAssert(dog.runSpeed == 100.9); + XCTAssert(dog.runSpeed == (float)100.9); } #pragma mark 过滤字典的值(比如字符串日期处理为NSDate、字符串nil处理为@"") diff --git a/MJExtensionTests/Model/MJBaseObject.h b/MJExtensionTests/Model/MJBaseObject.h index 085dc910..3625f4d4 100644 --- a/MJExtensionTests/Model/MJBaseObject.h +++ b/MJExtensionTests/Model/MJBaseObject.h @@ -8,6 +8,6 @@ #import -@interface MJBaseObject : NSObject +@interface MJBaseObject : NSObject @property (copy, nonatomic) NSString *name; @end diff --git a/MJExtensionTests/Model/MJCat.m b/MJExtensionTests/Model/MJCat.m index 8afd1b01..76aa31ee 100644 --- a/MJExtensionTests/Model/MJCat.m +++ b/MJExtensionTests/Model/MJCat.m @@ -13,7 +13,7 @@ @implementation MJCat -+ (NSDictionary *)mj_objectClassInCollection { ++ (NSDictionary *)mj_classInfoInCollection { return @{ @"nicknames" : NSString.class }; diff --git a/MJExtensionTests/Model/MJDog.h b/MJExtensionTests/Model/MJDog.h index 4e0b1c28..3efe3382 100644 --- a/MJExtensionTests/Model/MJDog.h +++ b/MJExtensionTests/Model/MJDog.h @@ -11,5 +11,5 @@ @interface MJDog : NSObject @property (copy, nonatomic) NSString *nickName; @property (assign, nonatomic) double salePrice; -@property (assign, nonatomic) double runSpeed; +@property (assign, nonatomic) float runSpeed; @end diff --git a/MJExtensionTests/Model/MJPerson.m b/MJExtensionTests/Model/MJPerson.m index 2469b538..629e242d 100644 --- a/MJExtensionTests/Model/MJPerson.m +++ b/MJExtensionTests/Model/MJPerson.m @@ -13,7 +13,7 @@ MJSecureCodingImplementation(MJPerson, YES) @implementation MJPerson -+ (NSDictionary *)mj_objectClassInCollection { ++ (NSDictionary *)mj_classInfoInCollection { return @{@"friends": MJPerson.class, @"books": NSString.class}; } diff --git a/MJExtensionTests/Model/MJStatusResult.m b/MJExtensionTests/Model/MJStatusResult.m index 39ff9d38..4ee21d86 100644 --- a/MJExtensionTests/Model/MJStatusResult.m +++ b/MJExtensionTests/Model/MJStatusResult.m @@ -10,7 +10,7 @@ #import "MJAd.h" @implementation MJStatusResult -+ (NSDictionary *)mj_objectClassInCollection { ++ (NSDictionary *)mj_classInfoInCollection { return @{ @"statuses" : @"MJStatus", @"ads" : MJAd.class // @"ads" : [MJAd class] diff --git a/MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan b/MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan index 87feb5c0..3b5348e4 100644 --- a/MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan +++ b/MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan @@ -20,8 +20,7 @@ { "parallelizable" : true, "skippedTests" : [ - "Benchmark", - "MultiThreadTests" + "Benchmark" ], "target" : { "containerPath" : "container:MJExtension.xcodeproj", diff --git a/MJExtensionTests/SwiftModel/MJCredential.swift b/MJExtensionTests/SwiftModel/MJCredential.swift index 93be3b75..293a1403 100644 --- a/MJExtensionTests/SwiftModel/MJCredential.swift +++ b/MJExtensionTests/SwiftModel/MJCredential.swift @@ -7,7 +7,10 @@ // import Foundation +@objc(MJCredential) @objcMembers class MJCredential: NSObject { var data: Data? + var instanceClass: AnyClass? + var selector: Selector? } diff --git a/MJExtensionTests/ValueTransformingTest.swift b/MJExtensionTests/ValueTransformingTest.swift deleted file mode 100644 index 8f64fc94..00000000 --- a/MJExtensionTests/ValueTransformingTest.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// ValueTransformingTest.swift -// MJExtensionTests -// -// Created by Frank on 2021/12/23. -// Copyright © 2021 MJ Lee. All rights reserved. -// - -import XCTest - -class ValueTransformingTest: XCTestCase { - func testString2Data() throws { - let dict: [String: Any] = [ - "data": "7355608" - ] - - guard let credential = MJCredential.mj_object(withKeyValues: dict) else { fatalError("credential conversion failed") } - guard let testString = dict["data"] as? String, - let testData = testString.data(using: .utf8) else { fatalError("8 bad data😭") } - - XCTAssertEqual(credential.data, testData) - } - - @available(iOS 15, *) - func testAttributedString2String() throws { - let article = MJArticle() - article.attributedTitle = try? NSAttributedString(markdown: "**m0nesy may help Niko get major trophy.**") - - guard let JSON = article.mj_JSONString() else { - fatalError("Object to JSON conversion failed") - } - - XCTAssertEqual(JSON, "{\"attributedTitle\":\"m0nesy may help Niko get major trophy.\"}") - } -} diff --git a/MJExtensionTests/ValueTransformingTests.swift b/MJExtensionTests/ValueTransformingTests.swift new file mode 100644 index 00000000..0dc0082c --- /dev/null +++ b/MJExtensionTests/ValueTransformingTests.swift @@ -0,0 +1,71 @@ +// +// ValueTransformingTests.swift +// MJExtensionTests +// +// Created by Frank on 2021/12/23. +// Copyright © 2021 MJ Lee. All rights reserved. +// + +import XCTest +import CoreLocation + +class ValueTransformingTests: XCTestCase { + func testString2Data() throws { + let dict: [String: Any] = [ + "data": "7355608" + ] + + guard let credential = MJCredential.mj_object(withKeyValues: dict) else { fatalError("credential conversion failed") } + guard let testString = dict["data"] as? String, + let testData = testString.data(using: .utf8) else { fatalError("8 bad data😭") } + + XCTAssertEqual(credential.data, testData) + } + + @available(iOS 15, *) + func testAttributedString2String() throws { + let article = MJArticle() + article.attributedTitle = try? NSAttributedString(markdown: "**m0nesy may help Niko get major trophy.**") + + guard let JSON = article.mj_JSONString() else { + fatalError("Object to JSON conversion failed") + } + + XCTAssertEqual(JSON, "{\"attributedTitle\":\"m0nesy may help Niko get major trophy.\"}") + } + + func testInfiniteDouble() throws { + let dict: [String: Any] = [ + "nick_name": "旺财", + "sale_price": "\(Double.greatestFiniteMagnitude)", + "run_speed": "\(Float.greatestFiniteMagnitude)", + ] + + guard let dog = MJDog.mj_object(withKeyValues: dict) else { fatalError("dog conversion failed") } + + XCTAssertEqual(dog.nickName, (dict["nick_name"] as! String)) + XCTAssertEqual(dog.salePrice, Double(dict["sale_price"] as! String)) + XCTAssertEqual(dog.runSpeed, Float(dict["run_speed"] as! String)) + } + + func testClass() throws { + let dict: [String: Any] = [ + "instanceClass": "MJCredential" + ] + + guard let credential = MJCredential.mj_object(withKeyValues: dict) else { fatalError("credential conversion failed") } + + XCTAssert(credential.instanceClass === MJCredential.self) + } + + func testSelector() throws { + let dict: [String: Any] = [ + "selector": "verifyToken:" + ] + + guard let credential = MJCredential.mj_object(withKeyValues: dict) else { fatalError("credential conversion failed") } + + XCTAssertEqual(credential.selector, Selector(dict["selector"] as! String)) + } + +}