Skip to content

Commit

Permalink
Fix infinite loop problem when converts CoreDataModel -> JSON. #839 #43
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfcon committed Jan 10, 2022
1 parent f05ed88 commit 97e2bab
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 35 deletions.
16 changes: 12 additions & 4 deletions MJExtension.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
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 */; };
018717B62787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018717B42787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataProperties.swift */; };
018717C32788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018717C12788552B0024B7F9 /* MJCDTesterInverseSelf+CoreDataClass.swift */; };
018717C42788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018717C22788552C0024B7F9 /* MJCDTesterInverseSelf+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 */; };
Expand Down Expand Up @@ -100,7 +102,9 @@
0179886B24EFA460007F7FBC /* MJTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MJTester.swift; sourceTree = "<group>"; };
0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftModelTests.swift; sourceTree = "<group>"; };
018717B32787E6CB0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataPerson+CoreDataClass.swift"; sourceTree = "<group>"; };
018717B42787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataPerson +CoreDataProperties.swift"; sourceTree = "<group>"; };
018717B42787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataPerson+CoreDataProperties.swift"; sourceTree = "<group>"; };
018717C12788552B0024B7F9 /* MJCDTesterInverseSelf+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCDTesterInverseSelf+CoreDataClass.swift"; sourceTree = "<group>"; };
018717C22788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCDTesterInverseSelf+CoreDataProperties.swift"; sourceTree = "<group>"; };
01BB00FC277EC1FF002EF5B3 /* DeprecatedAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPITests.swift; sourceTree = "<group>"; };
01BB00FE277EE8DB002EF5B3 /* NSDate+MJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+MJExtension.h"; sourceTree = "<group>"; };
01BB00FF277EE8DB002EF5B3 /* NSDate+MJExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDate+MJExtension.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -192,7 +196,9 @@
0107507926E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift */,
0130F20F2784441E00800FF3 /* MJCoreDataTester+CoreDataProperties.swift */,
018717B32787E6CB0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift */,
018717B42787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift */,
018717B42787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataProperties.swift */,
018717C12788552B0024B7F9 /* MJCDTesterInverseSelf+CoreDataClass.swift */,
018717C22788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataProperties.swift */,
);
path = CoreDataModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -556,7 +562,9 @@
buildActionMask = 2147483647;
files = (
2D2DBA812317DBE0005A689E /* MJStatusResult.m in Sources */,
018717C32788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataClass.swift in Sources */,
01BC91E627741D8C004E5265 /* MJArticle.swift in Sources */,
018717C42788552C0024B7F9 /* MJCDTesterInverseSelf+CoreDataProperties.swift in Sources */,
2D2DBA7C2317DBE0005A689E /* MJStudent.m in Sources */,
2D2DBA822317DBE0005A689E /* MJStatus.m in Sources */,
0130F2122784442200800FF3 /* MJCoreDataTester+CoreDataProperties.swift in Sources */,
Expand All @@ -581,7 +589,7 @@
0107507E26E890C100AAEA10 /* CoreDataTests.swift in Sources */,
018717B52787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataClass.swift in Sources */,
0107507826E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld in Sources */,
018717B62787E6CC0024B7F9 /* MJCoreDataPerson +CoreDataProperties.swift in Sources */,
018717B62787E6CC0024B7F9 /* MJCoreDataPerson+CoreDataProperties.swift in Sources */,
2D2DBA7A2317DBE0005A689E /* MJDog.m in Sources */,
01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */,
01BC91E427741956004E5265 /* MJCredential.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan">
reference = "container:MJExtensionTests/Plans/TestAllConversionCorrectness.xctestplan"
default = "YES">
</TestPlanReference>
<TestPlanReference
reference = "container:MJExtensionTests/Plans/MJExtension.xctestplan"
default = "YES">
reference = "container:MJExtensionTests/Plans/MJExtension.xctestplan">
</TestPlanReference>
</TestPlans>
<Testables>
Expand Down
2 changes: 2 additions & 0 deletions MJExtension/MJEClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
NSLocale * _Nullable _numberLocale;
NSDateFormatter * _Nullable _dateFormatter;
BOOL _shouldReferenceKeyReplacementInJSONExport;
/// Core Data class
BOOL _isNSManaged;
}

- (void)setNeedsUpdate;
Expand Down
8 changes: 5 additions & 3 deletions MJExtension/MJEClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ - (instancetype)initWithClass:(Class)cls {
if (!cls) return nil;
self = [super init];

_isNSManaged = [cls isSubclassOfClass:NSManagedObject.class];

// Check inheritance of configurations
BOOL shouldAutoInheritFromSuper = YES;
if ([cls respondsToSelector:@selector(mj_shouldAutoInheritConfigurations)]) {
Expand Down Expand Up @@ -184,9 +186,9 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList
replacedKeys:(NSDictionary *)replacedKeys
hasKeyReplacementModifier:(BOOL)hasKeyReplacementModifier
inClass:(Class)cls {
NSMutableArray<MJProperty *> *allProperties = [NSMutableArray array];
NSMutableArray<MJProperty *> *codingProperties = [NSMutableArray array];
NSMutableArray<MJProperty *> *allProperties2JSON = [NSMutableArray array];
NSMutableArray<MJProperty *> *allProperties = NSMutableArray.array;
NSMutableArray<MJProperty *> *codingProperties = NSMutableArray.array;
NSMutableArray<MJProperty *> *allProperties2JSON = NSMutableArray.array;
[cls mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1. get all property list
unsigned int outCount = 0;
Expand Down
2 changes: 1 addition & 1 deletion MJExtension/MJProperty.m
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ - (void)handleOriginKey:(id)originKey {
}
} else if ([originKey isKindOfClass:NSArray.class]) {
_isMultiMapping = YES;
NSMutableArray *keyses = [NSMutableArray array];
NSMutableArray *keyses = NSMutableArray.array;
for (NSString *stringKey in originKey) {
NSArray *multiKeys = stringKey.mj_multiKeys;
if (!multiKeys.count) continue;
Expand Down
2 changes: 1 addition & 1 deletion MJExtension/MJPropertyKey.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ - (MJPropertyKey *)propertyKey {
- (NSArray<MJPropertyKey *> *)mj_multiKeys {
if (self.length == 0) return nil;

NSMutableArray *multiKeys = [NSMutableArray array];
NSMutableArray *multiKeys = NSMutableArray.array;
// 如果有多级映射
NSArray *oldKeys = [self componentsSeparatedByString:@"."];

Expand Down
44 changes: 34 additions & 10 deletions MJExtension/NSObject+MJKeyValue.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ + (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:
if (MJE_isFromFoundation(self)) return [NSMutableArray arrayWithArray:keyValuesArray];

// 2.创建数组
NSMutableArray *modelArray = [NSMutableArray array];
NSMutableArray *modelArray = NSMutableArray.array;

// 3.遍历
for (NSDictionary *keyValues in keyValuesArray) {
Expand Down Expand Up @@ -183,13 +183,13 @@ - (id)mj_JSONObjectWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys i
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}

id object = [self mj_unsafe_JSONObjectWithRecursiveMode:isRecusiveMode keys:keys ignoredKeys:ignoredKeys];
id object = [self mj_unsafe_JSONObjectWithRecursiveMode:isRecusiveMode keys:keys ignoredKeys:ignoredKeys managedIDs:nil];
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 {
- (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys managedIDs:(NSMutableSet<NSManagedObjectID *> *)managedIDs {
if (self == NSNull.null) return NSNull.null;
if ([self isKindOfClass:NSString.class]) return self;
if ([self isKindOfClass:NSNumber.class]) {
Expand All @@ -209,7 +209,8 @@ - (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)ke
NSString *key = [anyKey isKindOfClass:NSString.class] ? anyKey : [anyKey description];
if (!key) continue;
id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES
keys:nil ignoredKeys:nil];
keys:nil ignoredKeys:nil
managedIDs:managedIDs];
if (!objJSON) continue;
result[key] = objJSON;
}
Expand All @@ -226,8 +227,13 @@ - (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)ke
if ([NSJSONSerialization isValidJSONObject:array]) return array;
NSMutableArray *result = [NSMutableArray new];
for (id obj in array) {
if ([obj isKindOfClass:NSManagedObject.class]) {
BOOL isloopPoint = [managedIDs containsObject:[obj objectID]];
if (isloopPoint) continue;
}
id objJSON = [obj mj_unsafe_JSONObjectWithRecursiveMode:YES
keys:nil ignoredKeys:nil];
keys:nil ignoredKeys:nil
managedIDs:managedIDs];
if (objJSON) [result addObject:objJSON];
}
return result;
Expand All @@ -241,8 +247,12 @@ - (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)ke
if (MJE_isFromFoundation(self.class)) return nil;

MJEClass *classCache = [MJEClass cachedClass:self.class];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSMutableDictionary *result = NSMutableDictionary.dictionary;
NSArray<MJProperty *> *allProperties = classCache->_allProperties;
BOOL selfIsManagedObject = classCache->_isNSManaged;

NSMutableSet<NSManagedObjectID *> *IDs;
if (selfIsManagedObject) IDs = managedIDs ?: NSMutableSet.set;

for (MJProperty *property in allProperties) {
// 0.检测是否被忽略
Expand All @@ -261,8 +271,22 @@ - (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)ke
if (!value) continue;

if (property.typeClass) {
// this is deadloop solution for inverse relationship.
// https://github.com/CoderMJLee/MJExtension/issues/839
if (selfIsManagedObject) {
NSManagedObject *mSelf = (NSManagedObject *)self;
BOOL hasRelationship = mSelf.entity.relationshipsByName[property.name].inverseRelationship;
if (hasRelationship) {
[IDs addObject:mSelf.objectID];
if ([value isKindOfClass:NSManagedObject.class]) {
BOOL isloopPoint = [IDs containsObject:[value objectID]];
if (isloopPoint) continue;
}
}
}
value = [value mj_unsafe_JSONObjectWithRecursiveMode:YES
keys:nil ignoredKeys:nil];
keys:nil ignoredKeys:nil
managedIDs:IDs];
} else {
switch (property.type) {
case MJEPropertyTypeClass: {
Expand Down Expand Up @@ -296,9 +320,9 @@ - (id)mj_unsafe_JSONObjectWithRecursiveMode:(BOOL)isRecursive keys:(NSArray *)ke
id tempInnerContainer = [propertyKey valueInObject:innerContainer];
if (tempInnerContainer == nil || tempInnerContainer == NSNull.null) {
if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
tempInnerContainer = [NSMutableDictionary dictionary];
tempInnerContainer = NSMutableDictionary.dictionary;
} else {
tempInnerContainer = [NSMutableArray array];
tempInnerContainer = NSMutableArray.array;
}
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = tempInnerContainer;
Expand Down Expand Up @@ -737,7 +761,7 @@ - (void)mj_slowEnumerateProperties:(NSArray<MJProperty *> *)properties
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
NSMutableArray *urlArray = NSMutableArray.array;
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// MJCDTesterInverseSelf+CoreDataClass.swift
// MJExtensionTests
//
// Created by Frank on 2022/1/7.
// Copyright © 2022 MJ Lee. All rights reserved.
//
//

import Foundation
import CoreData

@objc(MJCDTesterInverseSelf)
public class MJCDTesterInverseSelf: NSManagedObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// MJCDTesterInverseSelf+CoreDataProperties.swift
// MJExtensionTests
//
// Created by Frank on 2022/1/10.
// Copyright © 2022 MJ Lee. All rights reserved.
//
//

import Foundation
import CoreData


extension MJCDTesterInverseSelf {

@nonobjc public class func fetchRequest() -> NSFetchRequest<MJCDTesterInverseSelf> {
return NSFetchRequest<MJCDTesterInverseSelf>(entityName: "MJCDTesterInverseSelf")
}

@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<MJCDTesterInverseSelf>?

}

// MARK: Generated accessors for relatives
extension MJCDTesterInverseSelf {

@objc(addRelativesObject:)
@NSManaged public func addToRelatives(_ value: MJCDTesterInverseSelf)

@objc(removeRelativesObject:)
@NSManaged public func removeFromRelatives(_ value: MJCDTesterInverseSelf)

@objc(addRelatives:)
@NSManaged public func addToRelatives(_ values: NSSet)

@objc(removeRelatives:)
@NSManaged public func removeFromRelatives(_ values: NSSet)

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// MJCoreDataPerson+CoreDataProperties.swift
// MJExtensionTests
//
// Created by Frank on 2022/1/7.
// Created by Frank on 2022/1/10.
// Copyright © 2022 MJ Lee. All rights reserved.
//
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="MJCDTesterInverseSelf" representedClassName="MJCDTesterInverseSelf" syncable="YES">
<attribute name="age" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="identifier" optional="YES" attributeType="String"/>
<attribute name="isJuan" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="relatives" optional="YES" toMany="YES" deletionRule="No Action" destinationEntity="MJCDTesterInverseSelf" inverseName="relatives" inverseEntity="MJCDTesterInverseSelf"/>
<fetchedProperty name="bestJuans" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MJCDTesterInverseSelf"/>
</fetchedProperty>
</entity>
<entity name="Person" representedClassName="MJCoreDataPerson" syncable="YES">
<attribute name="age" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="tester" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tester" inverseName="relatives" inverseEntity="Tester"/>
<fetchedProperty name="bestJuans" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="Person"/>
</fetchedProperty>
</entity>
<entity name="Tester" representedClassName="MJCoreDataTester" syncable="YES">
<attribute name="age" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="identifier" optional="YES" attributeType="String"/>
<attribute name="isJuan" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="relatives" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Person" inverseName="tester" inverseEntity="Person"/>
<fetchedProperty name="bestJuans" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="Tester"/>
</fetchedProperty>
</entity>
<elements>
<element name="Tester" positionX="-63" positionY="-18" width="128" height="104"/>
<element name="Person" positionX="-45" positionY="45" width="128" height="74"/>
<element name="MJCDTesterInverseSelf" positionX="-36" positionY="63" width="128" height="125"/>
<element name="Person" positionX="-45" positionY="45" width="128" height="95"/>
<element name="Tester" positionX="-63" positionY="-18" width="128" height="125"/>
</elements>
</model>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import MJExtension
public class MJCoreDataTester: NSManagedObject, MJEConfiguration {
public static func mj_classInfoInCollection() -> [AnyHashable : Any]! {
return [
"relatives": MJCoreDataTester.self
"relatives": MJCoreDataPerson.self
]
}
}
Loading

0 comments on commit 97e2bab

Please sign in to comment.