From f05ed88b29188a73b642bc2a396b6ea2d72e69cb Mon Sep 17 00:00:00 2001 From: Frank <472730949@qq.com> Date: Fri, 7 Jan 2022 15:29:29 +0800 Subject: [PATCH] - Split private APIs - Add CoreData `inverse` test cases --- MJExtension.xcodeproj/project.pbxproj | 16 +- MJExtension/MJEClass.m | 2 +- MJExtension/MJExtensionProtocols.h | 2 +- MJExtension/MJExtension_Private.h | 36 + MJExtension/MJProperty.m | 61 +- MJExtension/MJPropertyKey.m | 50 ++ MJExtension/NSObject+MJKeyValue.h | 9 - MJExtension/NSObject+MJKeyValue.m | 754 +++++++++--------- MJExtension/NSString+MJExtension_Private.h | 17 - MJExtensionTests/Benchmark.swift | 3 +- ...MJCoreDataPerson +CoreDataProperties.swift | 24 + .../MJCoreDataPerson+CoreDataClass.swift | 16 + .../MJCoreDataTestModel.xcdatamodel/contents | 12 +- .../MJCoreDataTester+CoreDataProperties.swift | 8 +- MJExtensionTests/CoreDataTests.swift | 15 +- MJExtensionTests/DeprecatedAPITests.swift | 25 + .../MJExtensionTests-Bridging-Header.h | 1 + MJExtensionTests/MJExtensionTests.m | 58 -- MJExtensionTests/Model/MJFrenchUser.h | 3 + MJExtensionTests/Model/MJFrenchUser.m | 7 + MJExtensionTests/Model/MJUser.h | 2 +- MJExtensionTests/Model/MJUser.m | 4 + .../SpecialPropertyTypeTests.swift | 56 ++ MJExtensionTests/SwiftModelTests.swift | 6 +- 24 files changed, 644 insertions(+), 543 deletions(-) create mode 100644 MJExtension/MJExtension_Private.h delete mode 100644 MJExtension/NSString+MJExtension_Private.h create mode 100644 MJExtensionTests/CoreDataModel/MJCoreDataPerson +CoreDataProperties.swift create mode 100644 MJExtensionTests/CoreDataModel/MJCoreDataPerson+CoreDataClass.swift diff --git a/MJExtension.xcodeproj/project.pbxproj b/MJExtension.xcodeproj/project.pbxproj index 48501569..43f6d163 100644 --- a/MJExtension.xcodeproj/project.pbxproj +++ b/MJExtension.xcodeproj/project.pbxproj @@ -18,10 +18,12 @@ 0107509B26E8C5A300AAEA10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0107509926E8C5A300AAEA10 /* LaunchScreen.storyboard */; }; 010DF01E2761D90A0007EEF0 /* SpecialPropertyTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010DF01D2761D90A0007EEF0 /* SpecialPropertyTypeTests.swift */; }; 0130EE80233C56D8008D2386 /* MJFrenchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130EE7F233C56D8008D2386 /* MJFrenchUser.m */; }; - 0130F2032782DFD200800FF3 /* NSString+MJExtension_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0130F2022782DFA300800FF3 /* NSString+MJExtension_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 0130F2032782DFD200800FF3 /* MJExtension_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0130F2022782DFA300800FF3 /* MJExtension_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 0130F2122784442200800FF3 /* MJCoreDataTester+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0130F20F2784441E00800FF3 /* MJCoreDataTester+CoreDataProperties.swift */; }; 0179886C24EFA460007F7FBC /* MJTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886B24EFA460007F7FBC /* MJTester.swift */; }; 0179887024EFA58B007F7FBC /* SwiftModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */; }; + 018717B52787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018717B32787E6CB0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift */; }; + 018717B62787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018717B42787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.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 */; }; @@ -92,11 +94,13 @@ 010DF01D2761D90A0007EEF0 /* SpecialPropertyTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialPropertyTypeTests.swift; sourceTree = ""; }; 0130EE7E233C56D8008D2386 /* MJFrenchUser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJFrenchUser.h; sourceTree = ""; }; 0130EE7F233C56D8008D2386 /* MJFrenchUser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJFrenchUser.m; sourceTree = ""; }; - 0130F2022782DFA300800FF3 /* NSString+MJExtension_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+MJExtension_Private.h"; sourceTree = ""; }; + 0130F2022782DFA300800FF3 /* MJExtension_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJExtension_Private.h; sourceTree = ""; }; 0130F20F2784441E00800FF3 /* MJCoreDataTester+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataTester+CoreDataProperties.swift"; sourceTree = ""; }; 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 = ""; }; + 018717B32787E6CB0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataPerson+CoreDataClass.swift"; sourceTree = ""; }; + 018717B42787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataPerson +CoreDataProperties.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 = ""; }; @@ -187,6 +191,8 @@ 0107507626E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld */, 0107507926E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift */, 0130F20F2784441E00800FF3 /* MJCoreDataTester+CoreDataProperties.swift */, + 018717B32787E6CB0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift */, + 018717B42787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift */, ); path = CoreDataModel; sourceTree = ""; @@ -267,7 +273,7 @@ 2D2DBA062317DB32005A689E /* NSObject+MJKeyValue.m */, 2D2DBA142317DB33005A689E /* NSString+MJExtension.h */, 2D2DBA0B2317DB32005A689E /* NSString+MJExtension.mm */, - 0130F2022782DFA300800FF3 /* NSString+MJExtension_Private.h */, + 0130F2022782DFA300800FF3 /* MJExtension_Private.h */, 01BB00FE277EE8DB002EF5B3 /* NSDate+MJExtension.h */, 01BB00FF277EE8DB002EF5B3 /* NSDate+MJExtension.m */, 2D2DB9F62317DA64005A689E /* MJExtension.h */, @@ -350,7 +356,7 @@ 01F5515E2757144500518218 /* MJEClass.h in Headers */, 01BB0100277EE8DC002EF5B3 /* NSDate+MJExtension.h in Headers */, 2D2DB9F82317DA64005A689E /* MJExtension.h in Headers */, - 0130F2032782DFD200800FF3 /* NSString+MJExtension_Private.h in Headers */, + 0130F2032782DFD200800FF3 /* MJExtension_Private.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -573,7 +579,9 @@ 01BB00FD277EC1FF002EF5B3 /* DeprecatedAPITests.swift in Sources */, 2D2DBA7D2317DBE0005A689E /* MJBook.m in Sources */, 0107507E26E890C100AAEA10 /* CoreDataTests.swift in Sources */, + 018717B52787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift in Sources */, 0107507826E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld in Sources */, + 018717B62787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift in Sources */, 2D2DBA7A2317DBE0005A689E /* MJDog.m in Sources */, 01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */, 01BC91E427741956004E5265 /* MJCredential.swift in Sources */, diff --git a/MJExtension/MJEClass.m b/MJExtension/MJEClass.m index 789da94c..6ebaf654 100644 --- a/MJExtension/MJEClass.m +++ b/MJExtension/MJEClass.m @@ -10,6 +10,7 @@ #import "MJExtensionPredefine.h" #import "MJExtensionProtocols.h" #import "MJProperty.h" +#import "MJExtension_Private.h" typedef void (^MJClassesEnumeration)(Class c, BOOL *stop); @@ -20,7 +21,6 @@ + (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration; @implementation NSObject (MJEClass) -BOOL MJE_isFromFoundation(Class _Nonnull cls); + (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration { if (enumeration == nil) return; BOOL stop = NO; diff --git a/MJExtension/MJExtensionProtocols.h b/MJExtension/MJExtensionProtocols.h index 73619aff..354aba65 100644 --- a/MJExtension/MJExtensionProtocols.h +++ b/MJExtension/MJExtensionProtocols.h @@ -31,7 +31,7 @@ @end -@protocol MJECoding +@protocol MJECoding @optional /// Array for those properties that should only be allowed to coding. + (NSArray *)mj_allowedCodingPropertyNames; diff --git a/MJExtension/MJExtension_Private.h b/MJExtension/MJExtension_Private.h new file mode 100644 index 00000000..0d8fe089 --- /dev/null +++ b/MJExtension/MJExtension_Private.h @@ -0,0 +1,36 @@ +// +// MJExtension_Private.h +// MJExtension +// +// Created by Frank on 2022/1/3. +// Copyright © 2022 MJ Lee. All rights reserved. +// + +#ifndef MJExtension_Private_h +#define MJExtension_Private_h + +NS_ASSUME_NONNULL_BEGIN +@interface NSString (MJExtension_Private) + +@property (nonatomic, readonly) SEL mj_defaultSetter; + +@end + +@class MJPropertyKey; +@interface NSString (MJPropertyKey) + +/// If JSON key is "xxx.xxx", so add one more key for it. +- (MJPropertyKey *)propertyKey; + +/// Create keys with dot form, which is splitted by dot. +- (NSArray *)mj_multiKeys; + +@end + +@interface NSObject(MJExtension_Private) + +BOOL MJE_isFromFoundation(Class cls); + +@end +NS_ASSUME_NONNULL_END +#endif /* MJExtension_Private_h */ diff --git a/MJExtension/MJProperty.m b/MJExtension/MJProperty.m index c55f8183..3c269be9 100644 --- a/MJExtension/MJProperty.m +++ b/MJExtension/MJProperty.m @@ -11,69 +11,10 @@ #import #include "TargetConditionals.h" #import "NSString+MJExtension.h" -#import "NSString+MJExtension_Private.h" +#import "MJExtension_Private.h" #define mj_objGet(obj, type) mj_msgSendGet(obj, _getter, type) -@interface NSString (MJPropertyKey) - -/// If JSON key is "xxx.xxx", so add one more key for it. -- (MJPropertyKey *)propertyKey; - -/// Create keys with dot form, which is splitted by dot. -- (NSArray *)mj_multiKeys; - -@end - -@implementation NSString (MJPropertyKey) - -- (MJPropertyKey *)propertyKey { - MJPropertyKey *specialKey = [[MJPropertyKey alloc] init]; - specialKey.name = self; - return specialKey; -} - -- (NSArray *)mj_multiKeys { - if (self.length == 0) return nil; - - NSMutableArray *multiKeys = [NSMutableArray array]; - // 如果有多级映射 - NSArray *oldKeys = [self componentsSeparatedByString:@"."]; - - for (NSString *oldKey in oldKeys) { - NSUInteger start = [oldKey rangeOfString:@"["].location; - if (start != NSNotFound) { // 有索引的key - NSString *prefixKey = [oldKey substringToIndex:start]; - NSString *indexKey = prefixKey; - if (prefixKey.length) { - MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init]; - propertyKey.name = prefixKey; - [multiKeys addObject:propertyKey]; - - indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""]; - } - - /** 解析索引 **/ - // 元素 - NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"]; - for (NSInteger i = 0; i *)mj_multiKeys { + if (self.length == 0) return nil; + + NSMutableArray *multiKeys = [NSMutableArray array]; + // 如果有多级映射 + NSArray *oldKeys = [self componentsSeparatedByString:@"."]; + + for (NSString *oldKey in oldKeys) { + NSUInteger start = [oldKey rangeOfString:@"["].location; + if (start != NSNotFound) { // 有索引的key + NSString *prefixKey = [oldKey substringToIndex:start]; + NSString *indexKey = prefixKey; + if (prefixKey.length) { + MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init]; + propertyKey.name = prefixKey; + [multiKeys addObject:propertyKey]; + + indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""]; + } + + /** 解析索引 **/ + // 元素 + NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"]; + for (NSInteger i = 0; i @end @implementation NSObject (MJKeyValue) -BOOL MJE_isFromFoundation(Class _Nonnull cls) { - if (cls == NSObject.class || cls == NSManagedObject.class) return YES; - - static NSSet *foundationClasses; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // 集合中没有NSObject,因为几乎所有的类都是继承自NSObject,具体是不是NSObject需要特殊判断 - foundationClasses = [NSSet setWithObjects: - NSURL.class, - NSDate.class, - NSValue.class, - NSData.class, - NSArray.class, - NSDictionary.class, - NSString.class, - NSAttributedString.class, - NSSet.class, - NSOrderedSet.class, - NSError.class, nil]; - }); - - for (Class testedClass in foundationClasses) { - if ([cls isSubclassOfClass:testedClass]) return YES; - } - return NO; -} - -// Special dealing method. `value` should be NSString or NSNumber -- (NSNumber *)mj_numberWithValue:(id)value - type:(MJEPropertyType)type - locale:(NSLocale *)locale { - static NSDictionary *boolStrings; - static dispatch_once_t onceToken; - 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 - }; - }); - 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 = @([string mj_doubleValueWithLocale:locale]); - // LongDouble cannot be represented by NSNumber - } else if (type != MJEPropertyTypeLongDouble) { - number = [NSDecimalNumber - decimalNumberWithString:string locale:locale]; - if (number == NSDecimalNumber.notANumber) { - number = nil; - } - } - } - return number; -} - #pragma mark - 错误 static const char MJErrorKey = '\0'; + (NSError *)mj_error @@ -125,11 +41,10 @@ - (instancetype)mj_setKeyValues:(id)keyValues { return [self mj_setKeyValues:keyValues context:nil]; } -/** 核心代码: */ - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context { id object = keyValues; - if (![object isKindOfClass:NSDictionary.class]) object = [object mj_JSONObject]; + if (![object isKindOfClass:NSDictionary.class]) object = [object mj_shallowJSONObject]; MJExtensionAssertError([object isKindOfClass:NSDictionary.class], self, self.class, @"keyValues参数不是一个字典"); @@ -148,40 +63,315 @@ - (instancetype)mj_setKeyValues:(id)keyValues return self; } -- (void)mj_enumerateProperties:(NSArray *)properties - withDictionary:(NSDictionary *)dictionary - classCache:(MJEClass *)classCache - context:(NSManagedObjectContext *)context { - NSLocale *locale = classCache->_numberLocale; - 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; - } ++ (instancetype)mj_objectWithKeyValues:(id)keyValues +{ + return [self mj_objectWithKeyValues:keyValues context:nil]; +} + ++ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context { + id obj; + if ([self isSubclassOfClass:NSManagedObject.class] && context) { + NSString *entityName = [(NSManagedObject *)self entity].name; + obj = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; + } else { + obj = [self new]; + } + return [obj mj_setKeyValues:keyValues context:context]; +} + ++ (instancetype)mj_objectWithFilename:(NSString *)filename +{ + MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); + + return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; +} + ++ (instancetype)mj_objectWithFile:(NSString *)file +{ + MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); + + return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]]; +} + +#pragma mark - JSON -> Model Array ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray +{ + return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil]; +} + ++ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context { + // 如果是JSON字符串 + if (![keyValuesArray isKindOfClass:NSArray.class]) { + keyValuesArray = [keyValuesArray mj_shallowJSONObject]; + } + // 1.判断真实性 + MJExtensionAssertError([keyValuesArray isKindOfClass:NSArray.class], nil, [self class], @"keyValuesArray参数不是一个数组"); + + // 如果数组里面放的是NSString、NSNumber等数据 + if (MJE_isFromFoundation(self)) return [NSMutableArray arrayWithArray:keyValuesArray]; + + // 2.创建数组 + NSMutableArray *modelArray = [NSMutableArray array]; + + // 3.遍历 + for (NSDictionary *keyValues in keyValuesArray) { + if ([keyValues isKindOfClass:NSArray.class]){ + [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]]; + } else if ([keyValues isKindOfClass:NSDictionary.class]) { + id model = [self mj_objectWithKeyValues:keyValues context:context]; + if (model) [modelArray addObject:model]; } - - if (!value) continue; - if (value == NSNull.null) { - mj_selfSet(property, id, nil); - continue; + } + + return modelArray; +} + ++ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename +{ + MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); + + return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; +} + ++ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file +{ + MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); + + return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]]; +} + +#pragma mark - Any to JSON +- (NSData *)mj_JSONData { + id object = self.mj_JSONObject; + if (!object || object == NSNull.null) return nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:object options:kNilOptions error:nil]; + return data; +} + +- (NSString *)mj_JSONString { + NSData *data = self.mj_JSONData; + if (!data.length) return nil; + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return string; +} + +- (id)mj_JSONObject { + return [self mj_JSONObjectWithKeys:nil ignoredKeys:nil]; +} +- (id)mj_JSONObjectWithKeys:(NSArray *)keys { + return [self mj_JSONObjectWithKeys:keys ignoredKeys:nil]; +} +- (id)mj_JSONObjectWithIgnoredKeys:(NSArray *)ignoredKeys { + return [self mj_JSONObjectWithKeys:nil ignoredKeys:ignoredKeys]; +} + +- (id)mj_JSONObjectWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys { + return [self mj_JSONObjectWithKeys:keys ignoredKeys:ignoredKeys isRecursiveMode:YES]; +} + +#pragma mark - Private + +- (id)mj_shallowJSONObject { + return [self mj_JSONObjectWithKeys:nil + ignoredKeys:nil isRecursiveMode:NO]; +} + +- (id)mj_JSONObjectWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys isRecursiveMode:(BOOL)isRecusiveMode { + if ([self isKindOfClass:NSString.class]) { + return [NSJSONSerialization JSONObjectWithData:[(NSString *)self dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil]; + } else if ([self isKindOfClass:NSData.class]) { + return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil]; + } + + id object = [self mj_unsafe_JSONObjectWithRecursiveMode:isRecusiveMode keys:keys ignoredKeys:ignoredKeys]; + if ([object isKindOfClass:NSArray.class]) return object; + if ([object isKindOfClass:NSDictionary.class]) return object; + return nil; +} + +- (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys { + if (self == NSNull.null) return NSNull.null; + if ([self isKindOfClass:NSString.class]) return self; + if ([self isKindOfClass:NSNumber.class]) { + NSNumber *number = (NSNumber *)self; + // Inf and NaN should not be in JSON object. + if (isinf(number.doubleValue)) return @(0); + if ([number isEqualToNumber:NSDecimalNumber.notANumber]) return nil; + return self; + } + if ([self isKindOfClass:NSDictionary.class]) { + if (!isRecursive) return self; + if ([NSJSONSerialization isValidJSONObject:self]) return self; + NSDictionary *dictonary = (NSDictionary *)self; + NSMutableDictionary *result = [NSMutableDictionary new]; + for (id anyKey in dictonary.allKeys) { + id obj = dictonary[anyKey]; + NSString *key = [anyKey isKindOfClass:NSString.class] ? anyKey : [anyKey description]; + if (!key) continue; + id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES + keys:nil ignoredKeys:nil]; + if (!objJSON) continue; + result[key] = objJSON; + } + return result; + } + id array = self; + if ([self isKindOfClass:NSSet.class]) { + array = [(NSSet *)self allObjects]; + } else if ([self isKindOfClass:NSOrderedSet.class]) { + array = [(NSOrderedSet *)self array]; + } + if ([array isKindOfClass:NSArray.class]) { + if (!isRecursive) return self; + if ([NSJSONSerialization isValidJSONObject:array]) return array; + NSMutableArray *result = [NSMutableArray new]; + for (id obj in array) { + id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES + keys:nil ignoredKeys:nil]; + if (objJSON) [result addObject:objJSON]; + } + return result; + } + if ([self isKindOfClass:NSURL.class]) return [(NSURL *)self absoluteString]; + if ([self isKindOfClass:NSAttributedString.class]) return [(NSAttributedString *)self string]; + if ([self isKindOfClass:NSDate.class]) return [(NSDate *)self mj_defaultDateString]; + if ([self isKindOfClass:NSData.class]) return nil; + + // Check if the object is a "Model Object"(we defined) after basic type conversion finished + if (MJE_isFromFoundation(self.class)) return nil; + + MJEClass *classCache = [MJEClass cachedClass:self.class]; + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + NSArray *allProperties = classCache->_allProperties; + + for (MJProperty *property in allProperties) { + // 0.检测是否被忽略 + if (keys.count && ![keys containsObject:property.name]) continue; + if ([ignoredKeys containsObject:property.name]) continue; + + // 1.取出属性值 + id value; + if (!property->_isKVCCompliant) value = NSNull.null; + if (!property->_isBasicNumber) { + value = mj_selfGet(property, id); + } else { + value = [property numberForObject:self]; + } + + if (!value) continue; + + if (property.typeClass) { + value = [value mj_unsafe_JSONObjectWithRecursiveMode:YES + keys:nil ignoredKeys:nil]; + } else { + switch (property.type) { + case MJEPropertyTypeClass: { + Class cls = mj_selfGet(property, Class); + value = cls ? NSStringFromClass(cls) : nil; + } break; + case MJEPropertyTypeSEL: { + SEL selector = mj_selfGet(property, SEL); + value = selector ? NSStringFromSelector(selector) : nil; + } break; + default: break; + } + } + + // 4.赋值 + if (classCache->_shouldReferenceKeyReplacementInJSONExport) { + if (property->_isMultiMapping) { + NSArray *propertyKeys = [property->_mappedMultiKeys firstObject]; + NSUInteger keyCount = propertyKeys.count; + // 创建字典 + __block id innerContainer = result; + [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) { + // 下一个属性 + MJPropertyKey *nextPropertyKey = nil; + if (idx != keyCount - 1) { + nextPropertyKey = propertyKeys[idx + 1]; + } + + if (nextPropertyKey) { // 不是最后一个key + // 当前propertyKey对应的字典或者数组 + id tempInnerContainer = [propertyKey valueInObject:innerContainer]; + if (tempInnerContainer == nil || tempInnerContainer == NSNull.null) { + if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) { + tempInnerContainer = [NSMutableDictionary dictionary]; + } else { + tempInnerContainer = [NSMutableArray array]; + } + if (propertyKey.type == MJPropertyKeyTypeDictionary) { + innerContainer[propertyKey.name] = tempInnerContainer; + } else { + innerContainer[propertyKey.name.intValue] = tempInnerContainer; + } + } + + if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) { + NSMutableArray *tempInnerContainerArray = tempInnerContainer; + int index = nextPropertyKey.name.intValue; + while (tempInnerContainerArray.count < index + 1) { + [tempInnerContainerArray addObject:NSNull.null]; + } + } + + innerContainer = tempInnerContainer; + } else { // 最后一个key + if (propertyKey.type == MJPropertyKeyTypeDictionary) { + innerContainer[propertyKey.name] = value; + } else { + innerContainer[propertyKey.name.intValue] = value; + } + } + }]; + } else { + result[property->_mappedKey] = value; + } + } else { + result[property.name] = value; + } + } + + if (classCache->_hasObject2DictionaryModifier) { + [self mj_objectDidConvertToKeyValues:result]; + } + return result; +} + +- (void)mj_enumerateProperties:(NSArray *)properties + withDictionary:(NSDictionary *)dictionary + classCache:(MJEClass *)classCache + context:(NSManagedObjectContext *)context { + NSLocale *locale = classCache->_numberLocale; + for (MJProperty *property in properties) { + // get value from dictionary + id value = nil; + if (!property->_isMultiMapping) { + value = dictionary[property->_mappedKey]; + } else { + value = [property valueInDictionary:dictionary]; } - // convert as different cases - MJEPropertyType type = property.type; - Class typeClass = property.typeClass; - Class classInCollecion = property.classInCollection; - MJEBasicType basicObjectType = property->_basicObjectType; + + // 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 @@ -649,268 +839,88 @@ - (void)mj_slowEnumerateProperties:(NSArray *)properties } } -+ (instancetype)mj_objectWithKeyValues:(id)keyValues -{ - return [self mj_objectWithKeyValues:keyValues context:nil]; -} - -+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context { - id obj; - if ([self isSubclassOfClass:NSManagedObject.class] && context) { - NSString *entityName = [(NSManagedObject *)self entity].name; - obj = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context]; - } else { - obj = [self new]; - } - return [obj mj_setKeyValues:keyValues context:context]; -} - -+ (instancetype)mj_objectWithFilename:(NSString *)filename -{ - MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); - - return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; -} - -+ (instancetype)mj_objectWithFile:(NSString *)file -{ - MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); - - return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]]; -} - -#pragma mark - JSON -> Model Array -+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray -{ - return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil]; -} - -+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context { - // 如果是JSON字符串 - if (![keyValuesArray isKindOfClass:NSArray.class]) { - keyValuesArray = [keyValuesArray mj_JSONObject]; - } - // 1.判断真实性 - MJExtensionAssertError([keyValuesArray isKindOfClass:NSArray.class], nil, [self class], @"keyValuesArray参数不是一个数组"); - - // 如果数组里面放的是NSString、NSNumber等数据 - if (MJE_isFromFoundation(self)) return [NSMutableArray arrayWithArray:keyValuesArray]; - - // 2.创建数组 - NSMutableArray *modelArray = [NSMutableArray array]; - - // 3.遍历 - for (NSDictionary *keyValues in keyValuesArray) { - if ([keyValues isKindOfClass:NSArray.class]){ - [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]]; - } else if ([keyValues isKindOfClass:NSDictionary.class]) { - id model = [self mj_objectWithKeyValues:keyValues context:context]; - if (model) [modelArray addObject:model]; - } - } - - return modelArray; -} - -+ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename -{ - MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil"); +BOOL MJE_isFromFoundation(Class _Nonnull cls) { + if (cls == NSObject.class || cls == NSManagedObject.class) return YES; - return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]]; -} - -+ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file -{ - MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil"); + static NSSet *foundationClasses; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // 集合中没有NSObject,因为几乎所有的类都是继承自NSObject,具体是不是NSObject需要特殊判断 + foundationClasses = [NSSet setWithObjects: + NSURL.class, + NSDate.class, + NSValue.class, + NSData.class, + NSArray.class, + NSDictionary.class, + NSString.class, + NSAttributedString.class, + NSSet.class, + NSOrderedSet.class, + NSError.class, nil]; + }); - return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]]; -} - -#pragma mark - Any to JSON -- (NSData *)mj_JSONData { - id object = self.mj_JSONObject; - if (!object || object == NSNull.null) return nil; - NSData *data = [NSJSONSerialization dataWithJSONObject:object options:kNilOptions error:nil]; - return data; -} - -- (NSString *)mj_JSONString { - NSData *data = self.mj_JSONData; - if (!data.length) return nil; - NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - return string; -} - -- (id)mj_JSONObject { - return [self mj_JSONObjectWithKeys:nil ignoredKeys:nil]; -} -- (id)mj_JSONObjectWithKeys:(NSArray *)keys { - return [self mj_JSONObjectWithKeys:keys ignoredKeys:nil]; -} -- (id)mj_JSONObjectWithIgnoredKeys:(NSArray *)ignoredKeys { - return [self mj_JSONObjectWithKeys:nil ignoredKeys:ignoredKeys]; -} - -- (id)mj_JSONObjectWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys { - if ([self isKindOfClass:NSString.class]) { - return [NSJSONSerialization JSONObjectWithData:[(NSString *)self dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil]; - } else if ([self isKindOfClass:NSData.class]) { - return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil]; + for (Class testedClass in foundationClasses) { + if ([cls isSubclassOfClass:testedClass]) return YES; } - - id object = [self mj_unsafe_JSONObjectWithRecursiveMode:YES keys:keys ignoredKeys:ignoredKeys]; - if ([object isKindOfClass:NSArray.class]) return object; - if ([object isKindOfClass:NSDictionary.class]) return object; - return nil; + return NO; } -- (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys { - if (self == NSNull.null) return NSNull.null; - if ([self isKindOfClass:NSString.class]) return self; - if ([self isKindOfClass:NSNumber.class]) { - NSNumber *number = (NSNumber *)self; - // Inf and NaN should not be in JSON object. - if (isinf(number.doubleValue)) return @(0); - if ([number isEqualToNumber:NSDecimalNumber.notANumber]) return nil; - return self; - } - if ([self isKindOfClass:NSDictionary.class]) { - if (!isRecursive) return self; - if ([NSJSONSerialization isValidJSONObject:self]) return self; - NSDictionary *dictonary = (NSDictionary *)self; - NSMutableDictionary *result = [NSMutableDictionary new]; - for (id anyKey in dictonary.allKeys) { - id obj = dictonary[anyKey]; - NSString *key = [anyKey isKindOfClass:NSString.class] ? anyKey : [anyKey description]; - if (!key) continue; - id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES - keys:nil ignoredKeys:nil]; - if (!objJSON) continue; - result[key] = objJSON; - } - return result; - } - id array = self; - if ([self isKindOfClass:NSSet.class]) { - array = [(NSSet *)self allObjects]; - } else if ([self isKindOfClass:NSOrderedSet.class]) { - array = [(NSOrderedSet *)self array]; - } - if ([array isKindOfClass:NSArray.class]) { - if (!isRecursive) return self; - if ([NSJSONSerialization isValidJSONObject:array]) return array; - NSMutableArray *result = [NSMutableArray new]; - for (id obj in array) { - id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES - keys:nil ignoredKeys:nil]; - if (objJSON) [result addObject:objJSON]; - } - return result; - } - if ([self isKindOfClass:NSURL.class]) return [(NSURL *)self absoluteString]; - if ([self isKindOfClass:NSAttributedString.class]) return [(NSAttributedString *)self string]; - if ([self isKindOfClass:NSDate.class]) return [(NSDate *)self mj_defaultDateString]; - if ([self isKindOfClass:NSData.class]) return nil; - - // Check if the object is a "Model Object"(we defined) after basic type conversion finished - if (MJE_isFromFoundation(self.class)) return nil; - - MJEClass *classCache = [MJEClass cachedClass:self.class]; - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - NSArray *allProperties = classCache->_allProperties; - - for (MJProperty *property in allProperties) { - // 0.检测是否被忽略 - if (keys.count && ![keys containsObject:property.name]) continue; - if ([ignoredKeys containsObject:property.name]) continue; - - // 1.取出属性值 - id value; - if (!property->_isKVCCompliant) value = NSNull.null; - if (!property->_isBasicNumber) { - value = mj_selfGet(property, id); - } else { - value = [property numberForObject:self]; - } - - if (!value) continue; - - if (property.typeClass || property->_isBasicNumber) { - value = [value mj_unsafe_JSONObjectWithRecursiveMode:YES - keys:nil ignoredKeys:nil]; - } else { - switch (property.type) { - case MJEPropertyTypeClass: { - Class cls = mj_selfGet(property, Class); - value = cls ? NSStringFromClass(cls) : nil; - } break; - case MJEPropertyTypeSEL: { - SEL selector = mj_selfGet(property, SEL); - value = selector ? NSStringFromSelector(selector) : nil; - } break; - default: break; - } - } - - // 4.赋值 - if (classCache->_shouldReferenceKeyReplacementInJSONExport) { - if (property->_isMultiMapping) { - NSArray *propertyKeys = [property->_mappedMultiKeys firstObject]; - NSUInteger keyCount = propertyKeys.count; - // 创建字典 - __block id innerContainer = result; - [propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) { - // 下一个属性 - MJPropertyKey *nextPropertyKey = nil; - if (idx != keyCount - 1) { - nextPropertyKey = propertyKeys[idx + 1]; - } - - if (nextPropertyKey) { // 不是最后一个key - // 当前propertyKey对应的字典或者数组 - id tempInnerContainer = [propertyKey valueInObject:innerContainer]; - if (tempInnerContainer == nil || tempInnerContainer == NSNull.null) { - if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) { - tempInnerContainer = [NSMutableDictionary dictionary]; - } else { - tempInnerContainer = [NSMutableArray array]; - } - if (propertyKey.type == MJPropertyKeyTypeDictionary) { - innerContainer[propertyKey.name] = tempInnerContainer; - } else { - innerContainer[propertyKey.name.intValue] = tempInnerContainer; - } - } - - if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) { - NSMutableArray *tempInnerContainerArray = tempInnerContainer; - int index = nextPropertyKey.name.intValue; - while (tempInnerContainerArray.count < index + 1) { - [tempInnerContainerArray addObject:NSNull.null]; - } - } - - innerContainer = tempInnerContainer; - } else { // 最后一个key - if (propertyKey.type == MJPropertyKeyTypeDictionary) { - innerContainer[propertyKey.name] = value; - } else { - innerContainer[propertyKey.name.intValue] = value; - } - } - }]; - } else { - result[property->_mappedKey] = value; +// Special dealing method. `value` should be NSString or NSNumber +- (NSNumber *)mj_numberWithValue:(id)value + type:(MJEPropertyType)type + locale:(NSLocale *)locale { + static NSDictionary *boolStrings; + static dispatch_once_t onceToken; + 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 + }; + }); + 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 = @([string mj_doubleValueWithLocale:locale]); + // LongDouble cannot be represented by NSNumber + } else if (type != MJEPropertyTypeLongDouble) { + number = [NSDecimalNumber + decimalNumberWithString:string locale:locale]; + if (number == NSDecimalNumber.notANumber) { + number = nil; } - } else { - result[property.name] = value; } } - - if (classCache->_hasObject2DictionaryModifier) { - [self mj_objectDidConvertToKeyValues:result]; - } - return result; + return number; } @end diff --git a/MJExtension/NSString+MJExtension_Private.h b/MJExtension/NSString+MJExtension_Private.h deleted file mode 100644 index adef0442..00000000 --- a/MJExtension/NSString+MJExtension_Private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// NSString+MJExtension_Private.h -// MJExtension -// -// Created by Frank on 2022/1/3. -// Copyright © 2022 MJ Lee. All rights reserved. -// - -#ifndef NSString_MJExtension_Private_h -#define NSString_MJExtension_Private_h - -@interface NSString (MJExtension_Private) - -@property (nonatomic, readonly) SEL mj_defaultSetter; - -@end -#endif /* NSString_MJExtension_Private_h */ diff --git a/MJExtensionTests/Benchmark.swift b/MJExtensionTests/Benchmark.swift index c15791c3..372757c2 100644 --- a/MJExtensionTests/Benchmark.swift +++ b/MJExtensionTests/Benchmark.swift @@ -27,8 +27,7 @@ class Benchmark: XCTestCase { func testPerformanceLargeFile() { // should about 4.x s in my mac (instead of 17s before refactorring) self.measure { - let model = LargeModel.mj_objectArray(withKeyValuesArray: jsonObject) - print("MJ") + let _ = LargeModel.mj_objectArray(withKeyValuesArray: jsonObject) } } } diff --git a/MJExtensionTests/CoreDataModel/MJCoreDataPerson +CoreDataProperties.swift b/MJExtensionTests/CoreDataModel/MJCoreDataPerson +CoreDataProperties.swift new file mode 100644 index 00000000..c8588495 --- /dev/null +++ b/MJExtensionTests/CoreDataModel/MJCoreDataPerson +CoreDataProperties.swift @@ -0,0 +1,24 @@ +// +// MJCoreDataPerson+CoreDataProperties.swift +// MJExtensionTests +// +// Created by Frank on 2022/1/7. +// Copyright © 2022 MJ Lee. All rights reserved. +// +// + +import Foundation +import CoreData + + +extension MJCoreDataPerson { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Person") + } + + @NSManaged public var age: Int16 + @NSManaged public var name: String? + @NSManaged public var tester: MJCoreDataTester? + +} diff --git a/MJExtensionTests/CoreDataModel/MJCoreDataPerson+CoreDataClass.swift b/MJExtensionTests/CoreDataModel/MJCoreDataPerson+CoreDataClass.swift new file mode 100644 index 00000000..488701b3 --- /dev/null +++ b/MJExtensionTests/CoreDataModel/MJCoreDataPerson+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// MJCoreDataPerson+CoreDataClass.swift +// MJExtensionTests +// +// Created by Frank on 2022/1/7. +// Copyright © 2022 MJ Lee. All rights reserved. +// +// + +import Foundation +import CoreData + +@objc(MJCoreDataPerson) +public class MJCoreDataPerson: NSManagedObject { + +} diff --git a/MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents b/MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents index 8159bffc..22702d9a 100644 --- a/MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents +++ b/MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents @@ -1,13 +1,19 @@ - + + + + + + - + - + + \ No newline at end of file diff --git a/MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift b/MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift index dce2f60b..d2ce4e22 100644 --- a/MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift +++ b/MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift @@ -14,14 +14,14 @@ import CoreData extension MJCoreDataTester { @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "MJCDTester") + return NSFetchRequest(entityName: "Tester") } @NSManaged public var age: Int16 @NSManaged public var identifier: String? @NSManaged public var isJuan: Bool @NSManaged public var name: String? - @NSManaged public var relatives: Set? + @NSManaged public var relatives: Set? } @@ -29,10 +29,10 @@ extension MJCoreDataTester { extension MJCoreDataTester { @objc(addRelativesObject:) - @NSManaged public func addToRelatives(_ value: MJCoreDataTester) + @NSManaged public func addToRelatives(_ value: MJCoreDataPerson) @objc(removeRelativesObject:) - @NSManaged public func removeFromRelatives(_ value: MJCoreDataTester) + @NSManaged public func removeFromRelatives(_ value: MJCoreDataPerson) @objc(addRelatives:) @NSManaged public func addToRelatives(_ values: NSSet) diff --git a/MJExtensionTests/CoreDataTests.swift b/MJExtensionTests/CoreDataTests.swift index c08978a6..8939887b 100644 --- a/MJExtensionTests/CoreDataTests.swift +++ b/MJExtensionTests/CoreDataTests.swift @@ -62,8 +62,8 @@ class CoreDataTests: XCTestCase { XCTAssert(tester.age == Values.age) } - static func coreDataObject(in context: NSManagedObjectContext) -> MJCoreDataTester { - return NSEntityDescription.insertNewObject(forEntityName: MJCoreDataTester.entity().name!, into: context) as! MJCoreDataTester + static func coreDataObject(for type: T.Type, in context: NSManagedObjectContext) -> T { + return NSEntityDescription.insertNewObject(forEntityName: T.entity().name!, into: context) as! T } } @@ -83,10 +83,9 @@ class CoreDataTests: XCTestCase { for relative in relatives { switch relative.name { case Values.name: - Values.basicAssert(relative) + XCTAssert(tester.name == Values.name) + XCTAssert(tester.age == Values.age) case Values.broName: - XCTAssert(relative.isJuan == Values.isJuan) - XCTAssert(relative.identifier == Values.identifier) XCTAssert(relative.name == Values.broName) XCTAssert(relative.age == Values.broAge) default: break @@ -97,17 +96,15 @@ class CoreDataTests: XCTestCase { func testCoreDataObject2JSON() { context.performAndWait { - let coreDataObject = Values.coreDataObject(in: context) + let coreDataObject = Values.coreDataObject(for: MJCoreDataTester.self, in: context) coreDataObject.name = Values.name coreDataObject.isJuan = Values.isJuan coreDataObject.age = Values.age coreDataObject.identifier = Values.identifier - let broObject = Values.coreDataObject(in: context) + let broObject = Values.coreDataObject(for: MJCoreDataPerson.self, in: context) broObject.name = Values.broName - broObject.isJuan = Values.isJuan broObject.age = Values.broAge - broObject.identifier = Values.identifier coreDataObject.addToRelatives(broObject) guard let dict = coreDataObject.mj_JSONObject() as? [String: Any] else { diff --git a/MJExtensionTests/DeprecatedAPITests.swift b/MJExtensionTests/DeprecatedAPITests.swift index a7a29a80..cc4afa73 100644 --- a/MJExtensionTests/DeprecatedAPITests.swift +++ b/MJExtensionTests/DeprecatedAPITests.swift @@ -7,3 +7,28 @@ // import Foundation +import XCTest +import MJExtension; + +class DeprecatedAPITests: XCTestCase { + func testObjectClassInArray() throws { + let dict: [String: Any] = [ + "name": "mo", + "cats": [ + [ + "name": "mo🐱" + ], + [ + "name": "🐱mo" + ] + ] + ] + + guard let user = MJFrenchUser.mj_object(withKeyValues: dict) else { + fatalError("user conversion failed") + } + XCTAssertNotNil(user.cats) + XCTAssertEqual(user.cats.count, 2) + XCTAssert(user.cats.first!.isKind(of: MJCat.self)) + } +} diff --git a/MJExtensionTests/MJExtensionTests-Bridging-Header.h b/MJExtensionTests/MJExtensionTests-Bridging-Header.h index 6b376654..98081460 100644 --- a/MJExtensionTests/MJExtensionTests-Bridging-Header.h +++ b/MJExtensionTests/MJExtensionTests-Bridging-Header.h @@ -10,3 +10,4 @@ #import "MJDog.h" #import "MJBook.h" #import "MJBox.h" +#import "MJFrenchUser.h" diff --git a/MJExtensionTests/MJExtensionTests.m b/MJExtensionTests/MJExtensionTests.m index 267e6ca3..0872c741 100644 --- a/MJExtensionTests/MJExtensionTests.m +++ b/MJExtensionTests/MJExtensionTests.m @@ -673,62 +673,4 @@ - (void)testLogAllProperties { MJExtensionLog(@"%@", user); } - -#pragma mark 测试含有 UIColor 的属性 -- (void)testUIColorPropertyModel { - NSDictionary *catDict = @{ - @"name": @"五更琉璃", - @"address": [NSNull null], - @"nicknames": @[ - @"黑猫", - @"我老婆", - ], - @"color": @{ - @"systemColorName": @"blackColor" - }, - }; - MJCat *cat = [MJCat mj_objectWithKeyValues:catDict]; - - XCTAssertEqual(cat.name, catDict[@"name"]); - XCTAssertNil(cat.address); - XCTAssert(cat.nicknames.count == 2); - // 这个 Color 是不能被正常当普通的 UIColor 使用的. - XCTAssert(cat.color); -} - -- (void)testUIColor2JSON { - MJCat *ごこうるり = [[MJCat alloc] init]; - ごこうるり.name = @"五更瑠璃"; - ごこうるり.nicknames = @[@"黑猫", @"我老婆"]; - ごこうるり.color = UIColor.blackColor; - - NSDictionary *JSON = ごこうるり.mj_JSONObject; - XCTAssertEqual(JSON[@"name"], ごこうるり.name); - XCTAssert(((NSArray *)JSON[@"nicknames"]).count == 2); -} - -- (void)testUIColorCoding { - // 创建模型 - MJCat *master = [[MJCat alloc] init]; - master.name = @"要变了"; - master.color = UIColor.whiteColor; - master.nicknames = @[@"主人", @"陛下"]; - - NSString *file = [NSTemporaryDirectory() stringByAppendingPathComponent:@" cat.data"]; - NSError *error = nil; - // 归档 - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:master requiringSecureCoding:YES error:&error]; - BOOL isFinished = [data writeToFile:file atomically:true]; - XCTAssertNil(error); - XCTAssert(isFinished); - - // 解档 - NSData *readData = [NSFileManager.defaultManager contentsAtPath:file]; - error = nil; - MJCat *decodedMaster = [NSKeyedUnarchiver unarchivedObjectOfClass:MJCat.class fromData:readData error:&error]; - XCTAssertNil(error); - XCTAssert([decodedMaster.name isEqualToString:master.name]); - XCTAssert(decodedMaster.nicknames.count == 2); - XCTAssert(decodedMaster.color == master.color); -} @end diff --git a/MJExtensionTests/Model/MJFrenchUser.h b/MJExtensionTests/Model/MJFrenchUser.h index 68728c3c..e0d8e5d4 100644 --- a/MJExtensionTests/Model/MJFrenchUser.h +++ b/MJExtensionTests/Model/MJFrenchUser.h @@ -7,11 +7,14 @@ // #import "MJUser.h" +#import "MJCat.h" NS_ASSUME_NONNULL_BEGIN @interface MJFrenchUser : MJUser +@property (nonatomic) NSSet *cats; + @property (nonatomic) long double money_longDouble; @end diff --git a/MJExtensionTests/Model/MJFrenchUser.m b/MJExtensionTests/Model/MJFrenchUser.m index caddfc6d..2f37ab51 100644 --- a/MJExtensionTests/Model/MJFrenchUser.m +++ b/MJExtensionTests/Model/MJFrenchUser.m @@ -14,4 +14,11 @@ + (NSLocale *)mj_numberLocale { return [NSLocale localeWithLocaleIdentifier:@"fr_FR"];//ru_RU } +/// Depreacated API ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"cats": MJCat.class + }; +} + @end diff --git a/MJExtensionTests/Model/MJUser.h b/MJExtensionTests/Model/MJUser.h index 5ae5c308..82127073 100644 --- a/MJExtensionTests/Model/MJUser.h +++ b/MJExtensionTests/Model/MJUser.h @@ -13,7 +13,7 @@ typedef enum { SexFemale } Sex; -@interface MJUser : NSObject +@interface MJUser : NSObject /** 名称 */ @property (copy, nonatomic) NSString *name; /** 头像 */ diff --git a/MJExtensionTests/Model/MJUser.m b/MJExtensionTests/Model/MJUser.m index bcbc73c6..b0b63c12 100644 --- a/MJExtensionTests/Model/MJUser.m +++ b/MJExtensionTests/Model/MJUser.m @@ -10,6 +10,10 @@ @import MJExtension; +@interface MJUser() + +@end + @implementation MJUser //+ (NSArray *)mj_allowedPropertyNames { // return @[@"name", @"icon"]; diff --git a/MJExtensionTests/SpecialPropertyTypeTests.swift b/MJExtensionTests/SpecialPropertyTypeTests.swift index c597b8e6..a8f05759 100644 --- a/MJExtensionTests/SpecialPropertyTypeTests.swift +++ b/MJExtensionTests/SpecialPropertyTypeTests.swift @@ -21,4 +21,60 @@ class SpecialPropertyTypeTests: XCTestCase { XCTAssertEqual(bag?.price_longDouble, 205) } + + // MARK: 测试含有 UIColor 的属性 + func testUIColorPropertyModel() throws { + let catDict: AnyDictType = [ + "name": "五更琉璃", + "address": NSNull(), + "nicknames": ["黑猫", "我老婆"], + "color": [ + "systemColorName": "blackColor" + ] + ] + + guard let cat = MJCat.mj_object(withKeyValues: catDict) else { + fatalError("cat conversion failed") + } + XCTAssertEqual(cat.name, catDict["name"] as? String) + XCTAssertNil(cat.address) + XCTAssert(cat.nicknames?.count == 2) + // 这个 Color 是不能被正常当普通的 UIColor 使用的. + XCTAssertNotNil(cat.color) + } + + func testUIColor2JSON() throws { + let ごこうるり = MJCat() + ごこうるり.name = "五更瑠璃" + ごこうるり.nicknames = ["黑猫", "我老婆"] + ごこうるり.color = .black + + guard let JSON = ごこうるり.mj_JSONObject() as? AnyDictType else { + fatalError("UIColor model to JSON conversion failed") + } + XCTAssertEqual(JSON["name"] as? String, ごこうるり.name) + XCTAssert((JSON["nicknames"] as? [String])?.count == 2) + } + + func testUIColorCoding() throws { + // 创建模型 + let master = MJCat() + master.name = "要变了"; + master.color = .white; + master.nicknames = ["主人", "陛下"]; + + let file = NSTemporaryDirectory().appending("/cat.data") + let data = try NSKeyedArchiver.archivedData(withRootObject: master, requiringSecureCoding: true) as NSData + let isFinished = data.write(toFile: file, atomically: true) + XCTAssert(isFinished); + + // 解档 + let readData = FileManager.default.contents(atPath: file) + XCTAssertNotNil(readData) + let decodedMaster = try NSKeyedUnarchiver.unarchivedObject(ofClass: MJCat.self, from: readData!) + + XCTAssertEqual(decodedMaster?.name, master.name); + XCTAssertEqual(decodedMaster?.nicknames?.count, 2); + XCTAssertEqual(decodedMaster?.color, master.color); + } } diff --git a/MJExtensionTests/SwiftModelTests.swift b/MJExtensionTests/SwiftModelTests.swift index eb04597e..6a14e0f9 100644 --- a/MJExtensionTests/SwiftModelTests.swift +++ b/MJExtensionTests/SwiftModelTests.swift @@ -8,11 +8,13 @@ import XCTest +typealias AnyDictType = [String : Any] + class SwiftModelTests: XCTestCase { // MARK: 🌈 Use Swift model func testNormalModel() throws { - let testerDict: [String: Any] = [ + let testerDict: AnyDictType = [ "isSpecialAgent": true, "identifier": "007", "age": 22, @@ -56,7 +58,7 @@ class SwiftModelTests: XCTestCase { // MARK: 🌈 Use Objective-C model code func testOBJCModel() throws { - let userDict: [String: Any] = [ + let userDict: AnyDictType = [ "rich": true, "name": "007", "age": 22,