├── .gitignore ├── KVCMapping.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── KVCMappingTests.xcscheme ├── KVCMapping ├── KVCEntitiesCache.h ├── KVCEntitiesCache.m ├── KVCEntityMapping+AssignValue.h ├── KVCEntityMapping+AssignValue.m ├── KVCEntityMapping+Description.m ├── KVCEntityMapping+MappingDictionary.h ├── KVCEntityMapping+MappingDictionary.m ├── KVCEntityMapping.h ├── KVCEntityMapping.m ├── KVCMapping-Prefix.pch ├── KVCMapping.h ├── KVCMappingOptions.h ├── KVCMappingOptions.m ├── NSAttributeDescription+Coercion.h ├── NSAttributeDescription+Coercion.m ├── NSEntityDescription+KVCFetching.h ├── NSEntityDescription+KVCFetching.m ├── NSManagedObject+KVCRelationship.h ├── NSManagedObject+KVCRelationship.m ├── NSManagedObject+KVCSubobject.h ├── NSManagedObject+KVCSubobject.m ├── NSManagedObjectContext+KVCMapping.h ├── NSManagedObjectContext+KVCMapping.m ├── NSObject+KVCCollection.h ├── NSObject+KVCCollection.m ├── NSObject+KVCMapping.h └── NSObject+KVCMapping.m ├── LICENSE.md ├── README.mdown └── Tests ├── KVCEntitiesCacheTests.m ├── KVCMappingDictionaryTests.m ├── KVCMappingTests-Info.plist ├── NSAttributeDescription+CoercionTests.m ├── NSEntityDescription+KVCFetchingTests.m ├── NSManagedObject+KVCMappingTests.m ├── NSManagedObject+KVCRelationshipTests.m ├── NSManagedObject_KVCMapping_Tests.xcdatamodel └── contents └── NSObject+KVCMappingTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.xcuserdatad/ 3 | -------------------------------------------------------------------------------- /KVCMapping.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 37235E761662BCCB00F33BF1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37235E751662BCCB00F33BF1 /* Cocoa.framework */; }; 11 | 37235E851662BCE100F33BF1 /* NSObject+KVCMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EADF76150B8D3C001D3588 /* NSObject+KVCMapping.m */; }; 12 | 37235E881662BCE100F33BF1 /* NSManagedObject+KVCRelationship.m in Sources */ = {isa = PBXBuildFile; fileRef = 373D64FF1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m */; }; 13 | 373D65081657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h in Headers */ = {isa = PBXBuildFile; fileRef = 373D64FE1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h */; }; 14 | 373D65091657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m in Sources */ = {isa = PBXBuildFile; fileRef = 373D64FF1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m */; }; 15 | 373EE1231982CE5100062CD9 /* libKVCMapping-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37235E741662BCCB00F33BF1 /* libKVCMapping-osx.a */; }; 16 | 373EE1291982CE6100062CD9 /* NSObject+KVCMappingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EADF7E150B8E29001D3588 /* NSObject+KVCMappingTests.m */; }; 17 | 373EE12A1982CE6100062CD9 /* NSManagedObject+KVCMappingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 373D65131657F2100067FDC3 /* NSManagedObject+KVCMappingTests.m */; }; 18 | 373EE12B1982CE6100062CD9 /* KVCMappingDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB761743C97E00A7E7B9 /* KVCMappingDictionaryTests.m */; }; 19 | 373EE12C1982CE6100062CD9 /* NSAttributeDescription+CoercionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB4C1743C37800A7E7B9 /* NSAttributeDescription+CoercionTests.m */; }; 20 | 373EE12D1982CE6100062CD9 /* NSEntityDescription+KVCFetchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB4D1743C37800A7E7B9 /* NSEntityDescription+KVCFetchingTests.m */; }; 21 | 373EE12E1982CE6100062CD9 /* NSManagedObject+KVCRelationshipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 373D65141657F2100067FDC3 /* NSManagedObject+KVCRelationshipTests.m */; }; 22 | 373EE12F1982CE6100062CD9 /* KVCEntitiesCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB2B1743BF6C00A7E7B9 /* KVCEntitiesCacheTests.m */; }; 23 | 373EE1301982CE6100062CD9 /* NSManagedObject_KVCMapping_Tests.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 373D650F1657F2030067FDC3 /* NSManagedObject_KVCMapping_Tests.xcdatamodel */; }; 24 | 37A80DF2150B8CE800B9B6E1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37A80DF1150B8CE800B9B6E1 /* Foundation.framework */; }; 25 | 37EADF77150B8D3C001D3588 /* NSObject+KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 37EADF75150B8D3C001D3588 /* NSObject+KVCMapping.h */; }; 26 | 37EADF78150B8D3C001D3588 /* NSObject+KVCMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EADF76150B8D3C001D3588 /* NSObject+KVCMapping.m */; }; 27 | 37EADF7C150B8DAB001D3588 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EADF7B150B8DAB001D3588 /* CoreData.framework */; }; 28 | 8350CB261743BF4800A7E7B9 /* KVCEntitiesCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB241743BF4800A7E7B9 /* KVCEntitiesCache.h */; }; 29 | 8350CB271743BF4800A7E7B9 /* KVCEntitiesCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB241743BF4800A7E7B9 /* KVCEntitiesCache.h */; }; 30 | 8350CB281743BF4800A7E7B9 /* KVCEntitiesCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB251743BF4800A7E7B9 /* KVCEntitiesCache.m */; }; 31 | 8350CB291743BF4800A7E7B9 /* KVCEntitiesCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB251743BF4800A7E7B9 /* KVCEntitiesCache.m */; }; 32 | 8350CB301743C26E00A7E7B9 /* KVCEntityMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB2D1743C26E00A7E7B9 /* KVCEntityMapping.h */; }; 33 | 8350CB311743C26E00A7E7B9 /* KVCEntityMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB2D1743C26E00A7E7B9 /* KVCEntityMapping.h */; }; 34 | 8350CB321743C26E00A7E7B9 /* KVCEntityMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB2E1743C26E00A7E7B9 /* KVCEntityMapping.m */; }; 35 | 8350CB331743C26E00A7E7B9 /* KVCEntityMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB2E1743C26E00A7E7B9 /* KVCEntityMapping.m */; }; 36 | 8350CB351743C26E00A7E7B9 /* KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB2F1743C26E00A7E7B9 /* KVCMapping.h */; }; 37 | 8350CB361743C26E00A7E7B9 /* KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB2F1743C26E00A7E7B9 /* KVCMapping.h */; }; 38 | 8350CB3D1743C34600A7E7B9 /* NSAttributeDescription+Coercion.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB371743C34600A7E7B9 /* NSAttributeDescription+Coercion.h */; }; 39 | 8350CB3E1743C34600A7E7B9 /* NSAttributeDescription+Coercion.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB371743C34600A7E7B9 /* NSAttributeDescription+Coercion.h */; }; 40 | 8350CB3F1743C34600A7E7B9 /* NSAttributeDescription+Coercion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB381743C34600A7E7B9 /* NSAttributeDescription+Coercion.m */; }; 41 | 8350CB401743C34600A7E7B9 /* NSAttributeDescription+Coercion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB381743C34600A7E7B9 /* NSAttributeDescription+Coercion.m */; }; 42 | 8350CB421743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB391743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h */; }; 43 | 8350CB431743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB391743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h */; }; 44 | 8350CB441743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB3A1743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m */; }; 45 | 8350CB451743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB3A1743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m */; }; 46 | 8350CB471743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB3B1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h */; }; 47 | 8350CB481743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 8350CB3B1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h */; }; 48 | 8350CB491743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB3C1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m */; }; 49 | 8350CB4A1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 8350CB3C1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m */; }; 50 | 8350CB861743C9A800A7E7B9 /* NSManagedObject+KVCRelationship.h in Headers */ = {isa = PBXBuildFile; fileRef = 373D64FE1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h */; }; 51 | 8350CB871743C9B600A7E7B9 /* NSObject+KVCMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 37EADF75150B8D3C001D3588 /* NSObject+KVCMapping.h */; }; 52 | 8360BEFC174543D1002F095F /* KVCMappingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8360BEFA174543D1002F095F /* KVCMappingOptions.h */; }; 53 | 8360BEFD174543D1002F095F /* KVCMappingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8360BEFA174543D1002F095F /* KVCMappingOptions.h */; }; 54 | 8360BEFE174543D1002F095F /* KVCMappingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8360BEFB174543D1002F095F /* KVCMappingOptions.m */; }; 55 | 8360BEFF174543D1002F095F /* KVCMappingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8360BEFB174543D1002F095F /* KVCMappingOptions.m */; }; 56 | 83D4F85C174FBD8B0082A6DF /* KVCEntityMapping+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F859174FBD8A0082A6DF /* KVCEntityMapping+Description.m */; }; 57 | 83D4F85D174FBD8B0082A6DF /* KVCEntityMapping+Description.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F859174FBD8A0082A6DF /* KVCEntityMapping+Description.m */; }; 58 | 83D4F861174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F85F174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h */; }; 59 | 83D4F862174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F85F174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h */; }; 60 | 83D4F863174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F860174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m */; }; 61 | 83D4F864174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F860174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m */; }; 62 | 83D4F868174FC0550082A6DF /* KVCEntityMapping+AssignValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F866174FC0550082A6DF /* KVCEntityMapping+AssignValue.h */; }; 63 | 83D4F869174FC0550082A6DF /* KVCEntityMapping+AssignValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F866174FC0550082A6DF /* KVCEntityMapping+AssignValue.h */; }; 64 | 83D4F86A174FC0550082A6DF /* KVCEntityMapping+AssignValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F867174FC0550082A6DF /* KVCEntityMapping+AssignValue.m */; }; 65 | 83D4F86B174FC0550082A6DF /* KVCEntityMapping+AssignValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F867174FC0550082A6DF /* KVCEntityMapping+AssignValue.m */; }; 66 | 83D4F86F174FC3330082A6DF /* NSManagedObject+KVCSubobject.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F86D174FC3330082A6DF /* NSManagedObject+KVCSubobject.h */; }; 67 | 83D4F870174FC3330082A6DF /* NSManagedObject+KVCSubobject.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D4F86D174FC3330082A6DF /* NSManagedObject+KVCSubobject.h */; }; 68 | 83D4F871174FC3330082A6DF /* NSManagedObject+KVCSubobject.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F86E174FC3330082A6DF /* NSManagedObject+KVCSubobject.m */; }; 69 | 83D4F872174FC3330082A6DF /* NSManagedObject+KVCSubobject.m in Sources */ = {isa = PBXBuildFile; fileRef = 83D4F86E174FC3330082A6DF /* NSManagedObject+KVCSubobject.m */; }; 70 | A53A066F174FA9C800EFAE2F /* NSObject+KVCCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = A53A066D174FA9C800EFAE2F /* NSObject+KVCCollection.h */; }; 71 | A53A0670174FA9C800EFAE2F /* NSObject+KVCCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = A53A066D174FA9C800EFAE2F /* NSObject+KVCCollection.h */; }; 72 | A53A0671174FA9C800EFAE2F /* NSObject+KVCCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = A53A066E174FA9C800EFAE2F /* NSObject+KVCCollection.m */; }; 73 | A53A0672174FA9C800EFAE2F /* NSObject+KVCCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = A53A066E174FA9C800EFAE2F /* NSObject+KVCCollection.m */; }; 74 | /* End PBXBuildFile section */ 75 | 76 | /* Begin PBXContainerItemProxy section */ 77 | 373EE1241982CE5100062CD9 /* PBXContainerItemProxy */ = { 78 | isa = PBXContainerItemProxy; 79 | containerPortal = 37A80DE5150B8CE800B9B6E1 /* Project object */; 80 | proxyType = 1; 81 | remoteGlobalIDString = 37235E731662BCCB00F33BF1; 82 | remoteInfo = "KVCMapping-osx"; 83 | }; 84 | /* End PBXContainerItemProxy section */ 85 | 86 | /* Begin PBXFileReference section */ 87 | 37235E741662BCCB00F33BF1 /* libKVCMapping-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libKVCMapping-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 37235E751662BCCB00F33BF1 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 89 | 37235E781662BCCB00F33BF1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 90 | 37235E791662BCCB00F33BF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 91 | 37235E7A1662BCCB00F33BF1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 92 | 373D64FE1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+KVCRelationship.h"; sourceTree = ""; }; 93 | 373D64FF1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+KVCRelationship.m"; sourceTree = ""; }; 94 | 373D650F1657F2030067FDC3 /* NSManagedObject_KVCMapping_Tests.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = NSManagedObject_KVCMapping_Tests.xcdatamodel; sourceTree = ""; }; 95 | 373D65131657F2100067FDC3 /* NSManagedObject+KVCMappingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+KVCMappingTests.m"; sourceTree = ""; }; 96 | 373D65141657F2100067FDC3 /* NSManagedObject+KVCRelationshipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+KVCRelationshipTests.m"; sourceTree = ""; }; 97 | 373EE11D1982CE5100062CD9 /* KVCMappingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KVCMappingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | 37A80DEE150B8CE800B9B6E1 /* libKVCMapping-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libKVCMapping-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 37A80DF1150B8CE800B9B6E1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 100 | 37A80DF5150B8CE800B9B6E1 /* KVCMapping-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "KVCMapping-Prefix.pch"; sourceTree = ""; }; 101 | 37A80E09150B8CE800B9B6E1 /* KVCMappingTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "KVCMappingTests-Info.plist"; sourceTree = ""; }; 102 | 37EADF75150B8D3C001D3588 /* NSObject+KVCMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+KVCMapping.h"; sourceTree = ""; }; 103 | 37EADF76150B8D3C001D3588 /* NSObject+KVCMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+KVCMapping.m"; sourceTree = ""; }; 104 | 37EADF7B150B8DAB001D3588 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 105 | 37EADF7E150B8E29001D3588 /* NSObject+KVCMappingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+KVCMappingTests.m"; sourceTree = ""; }; 106 | 37EADF87150B91A0001D3588 /* README.mdown */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.mdown; sourceTree = ""; }; 107 | 8350CB241743BF4800A7E7B9 /* KVCEntitiesCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVCEntitiesCache.h; sourceTree = ""; }; 108 | 8350CB251743BF4800A7E7B9 /* KVCEntitiesCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVCEntitiesCache.m; sourceTree = ""; }; 109 | 8350CB2B1743BF6C00A7E7B9 /* KVCEntitiesCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVCEntitiesCacheTests.m; sourceTree = ""; }; 110 | 8350CB2D1743C26E00A7E7B9 /* KVCEntityMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVCEntityMapping.h; sourceTree = ""; }; 111 | 8350CB2E1743C26E00A7E7B9 /* KVCEntityMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVCEntityMapping.m; sourceTree = ""; }; 112 | 8350CB2F1743C26E00A7E7B9 /* KVCMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVCMapping.h; sourceTree = ""; }; 113 | 8350CB371743C34600A7E7B9 /* NSAttributeDescription+Coercion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributeDescription+Coercion.h"; sourceTree = ""; }; 114 | 8350CB381743C34600A7E7B9 /* NSAttributeDescription+Coercion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributeDescription+Coercion.m"; sourceTree = ""; }; 115 | 8350CB391743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSEntityDescription+KVCFetching.h"; sourceTree = ""; }; 116 | 8350CB3A1743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSEntityDescription+KVCFetching.m"; sourceTree = ""; }; 117 | 8350CB3B1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObjectContext+KVCMapping.h"; sourceTree = ""; }; 118 | 8350CB3C1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObjectContext+KVCMapping.m"; sourceTree = ""; }; 119 | 8350CB4C1743C37800A7E7B9 /* NSAttributeDescription+CoercionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributeDescription+CoercionTests.m"; sourceTree = ""; }; 120 | 8350CB4D1743C37800A7E7B9 /* NSEntityDescription+KVCFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSEntityDescription+KVCFetchingTests.m"; sourceTree = ""; }; 121 | 8350CB761743C97E00A7E7B9 /* KVCMappingDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVCMappingDictionaryTests.m; sourceTree = ""; }; 122 | 8360BEFA174543D1002F095F /* KVCMappingOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVCMappingOptions.h; sourceTree = ""; }; 123 | 8360BEFB174543D1002F095F /* KVCMappingOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KVCMappingOptions.m; sourceTree = ""; }; 124 | 83D4F859174FBD8A0082A6DF /* KVCEntityMapping+Description.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KVCEntityMapping+Description.m"; sourceTree = ""; }; 125 | 83D4F85F174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KVCEntityMapping+MappingDictionary.h"; sourceTree = ""; }; 126 | 83D4F860174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KVCEntityMapping+MappingDictionary.m"; sourceTree = ""; }; 127 | 83D4F866174FC0550082A6DF /* KVCEntityMapping+AssignValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KVCEntityMapping+AssignValue.h"; sourceTree = ""; }; 128 | 83D4F867174FC0550082A6DF /* KVCEntityMapping+AssignValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KVCEntityMapping+AssignValue.m"; sourceTree = ""; }; 129 | 83D4F86D174FC3330082A6DF /* NSManagedObject+KVCSubobject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+KVCSubobject.h"; sourceTree = ""; }; 130 | 83D4F86E174FC3330082A6DF /* NSManagedObject+KVCSubobject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+KVCSubobject.m"; sourceTree = ""; }; 131 | A53A066D174FA9C800EFAE2F /* NSObject+KVCCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+KVCCollection.h"; sourceTree = ""; }; 132 | A53A066E174FA9C800EFAE2F /* NSObject+KVCCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+KVCCollection.m"; sourceTree = ""; }; 133 | /* End PBXFileReference section */ 134 | 135 | /* Begin PBXFrameworksBuildPhase section */ 136 | 37235E711662BCCB00F33BF1 /* Frameworks */ = { 137 | isa = PBXFrameworksBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | 37235E761662BCCB00F33BF1 /* Cocoa.framework in Frameworks */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | 373EE11A1982CE5100062CD9 /* Frameworks */ = { 145 | isa = PBXFrameworksBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 373EE1231982CE5100062CD9 /* libKVCMapping-osx.a in Frameworks */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | 37A80DEB150B8CE800B9B6E1 /* Frameworks */ = { 153 | isa = PBXFrameworksBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | 37EADF7C150B8DAB001D3588 /* CoreData.framework in Frameworks */, 157 | 37A80DF2150B8CE800B9B6E1 /* Foundation.framework in Frameworks */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXFrameworksBuildPhase section */ 162 | 163 | /* Begin PBXGroup section */ 164 | 37235E771662BCCB00F33BF1 /* Other Frameworks */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 37235E781662BCCB00F33BF1 /* AppKit.framework */, 168 | 37235E791662BCCB00F33BF1 /* CoreData.framework */, 169 | 37235E7A1662BCCB00F33BF1 /* Foundation.framework */, 170 | ); 171 | name = "Other Frameworks"; 172 | sourceTree = ""; 173 | }; 174 | 37A80DE3150B8CE800B9B6E1 = { 175 | isa = PBXGroup; 176 | children = ( 177 | 37EADF87150B91A0001D3588 /* README.mdown */, 178 | 37A80DF3150B8CE800B9B6E1 /* KVCMapping */, 179 | 37A80E07150B8CE800B9B6E1 /* KVCMappingTests */, 180 | 37A80DF0150B8CE800B9B6E1 /* Frameworks */, 181 | 37A80DEF150B8CE800B9B6E1 /* Products */, 182 | ); 183 | sourceTree = ""; 184 | }; 185 | 37A80DEF150B8CE800B9B6E1 /* Products */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 37A80DEE150B8CE800B9B6E1 /* libKVCMapping-ios.a */, 189 | 37235E741662BCCB00F33BF1 /* libKVCMapping-osx.a */, 190 | 373EE11D1982CE5100062CD9 /* KVCMappingTests.xctest */, 191 | ); 192 | name = Products; 193 | sourceTree = ""; 194 | }; 195 | 37A80DF0150B8CE800B9B6E1 /* Frameworks */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 37A80DF1150B8CE800B9B6E1 /* Foundation.framework */, 199 | 37EADF7B150B8DAB001D3588 /* CoreData.framework */, 200 | 37235E751662BCCB00F33BF1 /* Cocoa.framework */, 201 | 37235E771662BCCB00F33BF1 /* Other Frameworks */, 202 | ); 203 | name = Frameworks; 204 | sourceTree = ""; 205 | }; 206 | 37A80DF3150B8CE800B9B6E1 /* KVCMapping */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 8350CB2F1743C26E00A7E7B9 /* KVCMapping.h */, 210 | 37EADF75150B8D3C001D3588 /* NSObject+KVCMapping.h */, 211 | 37EADF76150B8D3C001D3588 /* NSObject+KVCMapping.m */, 212 | 8360BEFA174543D1002F095F /* KVCMappingOptions.h */, 213 | 8360BEFB174543D1002F095F /* KVCMappingOptions.m */, 214 | 8350CB2D1743C26E00A7E7B9 /* KVCEntityMapping.h */, 215 | 8350CB2E1743C26E00A7E7B9 /* KVCEntityMapping.m */, 216 | 83D4F866174FC0550082A6DF /* KVCEntityMapping+AssignValue.h */, 217 | 83D4F867174FC0550082A6DF /* KVCEntityMapping+AssignValue.m */, 218 | 83D4F85F174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h */, 219 | 83D4F860174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m */, 220 | 83D4F859174FBD8A0082A6DF /* KVCEntityMapping+Description.m */, 221 | A53A066D174FA9C800EFAE2F /* NSObject+KVCCollection.h */, 222 | A53A066E174FA9C800EFAE2F /* NSObject+KVCCollection.m */, 223 | 8350CB371743C34600A7E7B9 /* NSAttributeDescription+Coercion.h */, 224 | 8350CB381743C34600A7E7B9 /* NSAttributeDescription+Coercion.m */, 225 | 373D64FE1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h */, 226 | 373D64FF1657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m */, 227 | 83D4F86D174FC3330082A6DF /* NSManagedObject+KVCSubobject.h */, 228 | 83D4F86E174FC3330082A6DF /* NSManagedObject+KVCSubobject.m */, 229 | 8350CB391743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h */, 230 | 8350CB3A1743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m */, 231 | 8350CB241743BF4800A7E7B9 /* KVCEntitiesCache.h */, 232 | 8350CB251743BF4800A7E7B9 /* KVCEntitiesCache.m */, 233 | 8350CB3B1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h */, 234 | 8350CB3C1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m */, 235 | 37A80DF4150B8CE800B9B6E1 /* Supporting Files */, 236 | ); 237 | path = KVCMapping; 238 | sourceTree = ""; 239 | }; 240 | 37A80DF4150B8CE800B9B6E1 /* Supporting Files */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | 37A80DF5150B8CE800B9B6E1 /* KVCMapping-Prefix.pch */, 244 | ); 245 | name = "Supporting Files"; 246 | sourceTree = ""; 247 | }; 248 | 37A80E07150B8CE800B9B6E1 /* KVCMappingTests */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | 37EADF7E150B8E29001D3588 /* NSObject+KVCMappingTests.m */, 252 | 373D65131657F2100067FDC3 /* NSManagedObject+KVCMappingTests.m */, 253 | 8350CB761743C97E00A7E7B9 /* KVCMappingDictionaryTests.m */, 254 | 8350CB4C1743C37800A7E7B9 /* NSAttributeDescription+CoercionTests.m */, 255 | 8350CB4D1743C37800A7E7B9 /* NSEntityDescription+KVCFetchingTests.m */, 256 | 373D65141657F2100067FDC3 /* NSManagedObject+KVCRelationshipTests.m */, 257 | 8350CB2B1743BF6C00A7E7B9 /* KVCEntitiesCacheTests.m */, 258 | 373D650F1657F2030067FDC3 /* NSManagedObject_KVCMapping_Tests.xcdatamodel */, 259 | 37A80E08150B8CE800B9B6E1 /* Supporting Files */, 260 | ); 261 | name = KVCMappingTests; 262 | path = Tests; 263 | sourceTree = ""; 264 | }; 265 | 37A80E08150B8CE800B9B6E1 /* Supporting Files */ = { 266 | isa = PBXGroup; 267 | children = ( 268 | 37A80E09150B8CE800B9B6E1 /* KVCMappingTests-Info.plist */, 269 | ); 270 | name = "Supporting Files"; 271 | sourceTree = ""; 272 | }; 273 | /* End PBXGroup section */ 274 | 275 | /* Begin PBXHeadersBuildPhase section */ 276 | 37235E721662BCCB00F33BF1 /* Headers */ = { 277 | isa = PBXHeadersBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 8350CB271743BF4800A7E7B9 /* KVCEntitiesCache.h in Headers */, 281 | 8350CB311743C26E00A7E7B9 /* KVCEntityMapping.h in Headers */, 282 | 8350CB361743C26E00A7E7B9 /* KVCMapping.h in Headers */, 283 | 8350CB3E1743C34600A7E7B9 /* NSAttributeDescription+Coercion.h in Headers */, 284 | 8350CB431743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h in Headers */, 285 | 8350CB481743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h in Headers */, 286 | 8350CB861743C9A800A7E7B9 /* NSManagedObject+KVCRelationship.h in Headers */, 287 | 8350CB871743C9B600A7E7B9 /* NSObject+KVCMapping.h in Headers */, 288 | 8360BEFD174543D1002F095F /* KVCMappingOptions.h in Headers */, 289 | A53A0670174FA9C800EFAE2F /* NSObject+KVCCollection.h in Headers */, 290 | 83D4F862174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h in Headers */, 291 | 83D4F869174FC0550082A6DF /* KVCEntityMapping+AssignValue.h in Headers */, 292 | 83D4F870174FC3330082A6DF /* NSManagedObject+KVCSubobject.h in Headers */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | 37A80DEC150B8CE800B9B6E1 /* Headers */ = { 297 | isa = PBXHeadersBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 37EADF77150B8D3C001D3588 /* NSObject+KVCMapping.h in Headers */, 301 | 373D65081657F1C80067FDC3 /* NSManagedObject+KVCRelationship.h in Headers */, 302 | 8350CB261743BF4800A7E7B9 /* KVCEntitiesCache.h in Headers */, 303 | 8350CB301743C26E00A7E7B9 /* KVCEntityMapping.h in Headers */, 304 | 8350CB351743C26E00A7E7B9 /* KVCMapping.h in Headers */, 305 | 8350CB3D1743C34600A7E7B9 /* NSAttributeDescription+Coercion.h in Headers */, 306 | 8350CB421743C34600A7E7B9 /* NSEntityDescription+KVCFetching.h in Headers */, 307 | 8350CB471743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.h in Headers */, 308 | 8360BEFC174543D1002F095F /* KVCMappingOptions.h in Headers */, 309 | A53A066F174FA9C800EFAE2F /* NSObject+KVCCollection.h in Headers */, 310 | 83D4F861174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.h in Headers */, 311 | 83D4F868174FC0550082A6DF /* KVCEntityMapping+AssignValue.h in Headers */, 312 | 83D4F86F174FC3330082A6DF /* NSManagedObject+KVCSubobject.h in Headers */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXHeadersBuildPhase section */ 317 | 318 | /* Begin PBXNativeTarget section */ 319 | 37235E731662BCCB00F33BF1 /* KVCMapping-osx */ = { 320 | isa = PBXNativeTarget; 321 | buildConfigurationList = 37235E831662BCCC00F33BF1 /* Build configuration list for PBXNativeTarget "KVCMapping-osx" */; 322 | buildPhases = ( 323 | 37235E701662BCCB00F33BF1 /* Sources */, 324 | 37235E711662BCCB00F33BF1 /* Frameworks */, 325 | 37235E721662BCCB00F33BF1 /* Headers */, 326 | ); 327 | buildRules = ( 328 | ); 329 | dependencies = ( 330 | ); 331 | name = "KVCMapping-osx"; 332 | productName = "KVCMapping-osx"; 333 | productReference = 37235E741662BCCB00F33BF1 /* libKVCMapping-osx.a */; 334 | productType = "com.apple.product-type.library.static"; 335 | }; 336 | 373EE11C1982CE5100062CD9 /* KVCMappingTests */ = { 337 | isa = PBXNativeTarget; 338 | buildConfigurationList = 373EE1261982CE5100062CD9 /* Build configuration list for PBXNativeTarget "KVCMappingTests" */; 339 | buildPhases = ( 340 | 373EE1191982CE5100062CD9 /* Sources */, 341 | 373EE11A1982CE5100062CD9 /* Frameworks */, 342 | 373EE11B1982CE5100062CD9 /* Resources */, 343 | ); 344 | buildRules = ( 345 | ); 346 | dependencies = ( 347 | 373EE1251982CE5100062CD9 /* PBXTargetDependency */, 348 | ); 349 | name = KVCMappingTests; 350 | productName = KVCMappingTests; 351 | productReference = 373EE11D1982CE5100062CD9 /* KVCMappingTests.xctest */; 352 | productType = "com.apple.product-type.bundle.unit-test"; 353 | }; 354 | 37A80DED150B8CE800B9B6E1 /* KVCMapping-ios */ = { 355 | isa = PBXNativeTarget; 356 | buildConfigurationList = 37A80E12150B8CE800B9B6E1 /* Build configuration list for PBXNativeTarget "KVCMapping-ios" */; 357 | buildPhases = ( 358 | 37A80DEA150B8CE800B9B6E1 /* Sources */, 359 | 37A80DEB150B8CE800B9B6E1 /* Frameworks */, 360 | 37A80DEC150B8CE800B9B6E1 /* Headers */, 361 | ); 362 | buildRules = ( 363 | ); 364 | dependencies = ( 365 | ); 366 | name = "KVCMapping-ios"; 367 | productName = KVCMapping; 368 | productReference = 37A80DEE150B8CE800B9B6E1 /* libKVCMapping-ios.a */; 369 | productType = "com.apple.product-type.library.static"; 370 | }; 371 | /* End PBXNativeTarget section */ 372 | 373 | /* Begin PBXProject section */ 374 | 37A80DE5150B8CE800B9B6E1 /* Project object */ = { 375 | isa = PBXProject; 376 | attributes = { 377 | LastUpgradeCheck = 0600; 378 | TargetAttributes = { 379 | 373EE11C1982CE5100062CD9 = { 380 | CreatedOnToolsVersion = 6.0; 381 | TestTargetID = 37235E731662BCCB00F33BF1; 382 | }; 383 | }; 384 | }; 385 | buildConfigurationList = 37A80DE8150B8CE800B9B6E1 /* Build configuration list for PBXProject "KVCMapping" */; 386 | compatibilityVersion = "Xcode 3.2"; 387 | developmentRegion = English; 388 | hasScannedForEncodings = 0; 389 | knownRegions = ( 390 | en, 391 | ); 392 | mainGroup = 37A80DE3150B8CE800B9B6E1; 393 | productRefGroup = 37A80DEF150B8CE800B9B6E1 /* Products */; 394 | projectDirPath = ""; 395 | projectRoot = ""; 396 | targets = ( 397 | 37A80DED150B8CE800B9B6E1 /* KVCMapping-ios */, 398 | 37235E731662BCCB00F33BF1 /* KVCMapping-osx */, 399 | 373EE11C1982CE5100062CD9 /* KVCMappingTests */, 400 | ); 401 | }; 402 | /* End PBXProject section */ 403 | 404 | /* Begin PBXResourcesBuildPhase section */ 405 | 373EE11B1982CE5100062CD9 /* Resources */ = { 406 | isa = PBXResourcesBuildPhase; 407 | buildActionMask = 2147483647; 408 | files = ( 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | /* End PBXResourcesBuildPhase section */ 413 | 414 | /* Begin PBXSourcesBuildPhase section */ 415 | 37235E701662BCCB00F33BF1 /* Sources */ = { 416 | isa = PBXSourcesBuildPhase; 417 | buildActionMask = 2147483647; 418 | files = ( 419 | 37235E851662BCE100F33BF1 /* NSObject+KVCMapping.m in Sources */, 420 | 37235E881662BCE100F33BF1 /* NSManagedObject+KVCRelationship.m in Sources */, 421 | 8350CB291743BF4800A7E7B9 /* KVCEntitiesCache.m in Sources */, 422 | 8350CB331743C26E00A7E7B9 /* KVCEntityMapping.m in Sources */, 423 | 8350CB401743C34600A7E7B9 /* NSAttributeDescription+Coercion.m in Sources */, 424 | 8350CB451743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m in Sources */, 425 | 8350CB4A1743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m in Sources */, 426 | 8360BEFF174543D1002F095F /* KVCMappingOptions.m in Sources */, 427 | A53A0672174FA9C800EFAE2F /* NSObject+KVCCollection.m in Sources */, 428 | 83D4F85D174FBD8B0082A6DF /* KVCEntityMapping+Description.m in Sources */, 429 | 83D4F864174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m in Sources */, 430 | 83D4F86B174FC0550082A6DF /* KVCEntityMapping+AssignValue.m in Sources */, 431 | 83D4F872174FC3330082A6DF /* NSManagedObject+KVCSubobject.m in Sources */, 432 | ); 433 | runOnlyForDeploymentPostprocessing = 0; 434 | }; 435 | 373EE1191982CE5100062CD9 /* Sources */ = { 436 | isa = PBXSourcesBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | 373EE12B1982CE6100062CD9 /* KVCMappingDictionaryTests.m in Sources */, 440 | 373EE12D1982CE6100062CD9 /* NSEntityDescription+KVCFetchingTests.m in Sources */, 441 | 373EE1291982CE6100062CD9 /* NSObject+KVCMappingTests.m in Sources */, 442 | 373EE12C1982CE6100062CD9 /* NSAttributeDescription+CoercionTests.m in Sources */, 443 | 373EE12E1982CE6100062CD9 /* NSManagedObject+KVCRelationshipTests.m in Sources */, 444 | 373EE12F1982CE6100062CD9 /* KVCEntitiesCacheTests.m in Sources */, 445 | 373EE1301982CE6100062CD9 /* NSManagedObject_KVCMapping_Tests.xcdatamodel in Sources */, 446 | 373EE12A1982CE6100062CD9 /* NSManagedObject+KVCMappingTests.m in Sources */, 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | 37A80DEA150B8CE800B9B6E1 /* Sources */ = { 451 | isa = PBXSourcesBuildPhase; 452 | buildActionMask = 2147483647; 453 | files = ( 454 | 37EADF78150B8D3C001D3588 /* NSObject+KVCMapping.m in Sources */, 455 | 373D65091657F1C80067FDC3 /* NSManagedObject+KVCRelationship.m in Sources */, 456 | 8350CB281743BF4800A7E7B9 /* KVCEntitiesCache.m in Sources */, 457 | 8350CB321743C26E00A7E7B9 /* KVCEntityMapping.m in Sources */, 458 | 8350CB3F1743C34600A7E7B9 /* NSAttributeDescription+Coercion.m in Sources */, 459 | 8350CB441743C34600A7E7B9 /* NSEntityDescription+KVCFetching.m in Sources */, 460 | 8350CB491743C34600A7E7B9 /* NSManagedObjectContext+KVCMapping.m in Sources */, 461 | 8360BEFE174543D1002F095F /* KVCMappingOptions.m in Sources */, 462 | A53A0671174FA9C800EFAE2F /* NSObject+KVCCollection.m in Sources */, 463 | 83D4F85C174FBD8B0082A6DF /* KVCEntityMapping+Description.m in Sources */, 464 | 83D4F863174FBEEA0082A6DF /* KVCEntityMapping+MappingDictionary.m in Sources */, 465 | 83D4F86A174FC0550082A6DF /* KVCEntityMapping+AssignValue.m in Sources */, 466 | 83D4F871174FC3330082A6DF /* NSManagedObject+KVCSubobject.m in Sources */, 467 | ); 468 | runOnlyForDeploymentPostprocessing = 0; 469 | }; 470 | /* End PBXSourcesBuildPhase section */ 471 | 472 | /* Begin PBXTargetDependency section */ 473 | 373EE1251982CE5100062CD9 /* PBXTargetDependency */ = { 474 | isa = PBXTargetDependency; 475 | target = 37235E731662BCCB00F33BF1 /* KVCMapping-osx */; 476 | targetProxy = 373EE1241982CE5100062CD9 /* PBXContainerItemProxy */; 477 | }; 478 | /* End PBXTargetDependency section */ 479 | 480 | /* Begin XCBuildConfiguration section */ 481 | 37235E811662BCCC00F33BF1 /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | COMBINE_HIDPI_IMAGES = YES; 485 | MACOSX_DEPLOYMENT_TARGET = 10.8; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SDKROOT = macosx; 488 | SKIP_INSTALL = YES; 489 | }; 490 | name = Debug; 491 | }; 492 | 37235E821662BCCC00F33BF1 /* Release */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | COMBINE_HIDPI_IMAGES = YES; 496 | MACOSX_DEPLOYMENT_TARGET = 10.8; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SDKROOT = macosx; 499 | SKIP_INSTALL = YES; 500 | }; 501 | name = Release; 502 | }; 503 | 373EE1271982CE5100062CD9 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 507 | CLANG_CXX_LIBRARY = "libc++"; 508 | CLANG_ENABLE_MODULES = YES; 509 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 510 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 511 | COMBINE_HIDPI_IMAGES = YES; 512 | FRAMEWORK_SEARCH_PATHS = ( 513 | "$(DEVELOPER_FRAMEWORKS_DIR)", 514 | "$(inherited)", 515 | ); 516 | GCC_PREPROCESSOR_DEFINITIONS = ( 517 | "DEBUG=1", 518 | "$(inherited)", 519 | ); 520 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 521 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 522 | INFOPLIST_FILE = "Tests/KVCMappingTests-Info.plist"; 523 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 524 | MACOSX_DEPLOYMENT_TARGET = 10.9; 525 | MTL_ENABLE_DEBUG_INFO = YES; 526 | PRODUCT_NAME = "$(TARGET_NAME)"; 527 | SDKROOT = macosx; 528 | }; 529 | name = Debug; 530 | }; 531 | 373EE1281982CE5100062CD9 /* Release */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 535 | CLANG_CXX_LIBRARY = "libc++"; 536 | CLANG_ENABLE_MODULES = YES; 537 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 538 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 539 | COMBINE_HIDPI_IMAGES = YES; 540 | COPY_PHASE_STRIP = YES; 541 | ENABLE_NS_ASSERTIONS = NO; 542 | FRAMEWORK_SEARCH_PATHS = ( 543 | "$(DEVELOPER_FRAMEWORKS_DIR)", 544 | "$(inherited)", 545 | ); 546 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 547 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 548 | INFOPLIST_FILE = "Tests/KVCMappingTests-Info.plist"; 549 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 550 | MACOSX_DEPLOYMENT_TARGET = 10.9; 551 | MTL_ENABLE_DEBUG_INFO = NO; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | SDKROOT = macosx; 554 | }; 555 | name = Release; 556 | }; 557 | 37A80E10150B8CE800B9B6E1 /* Debug */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | ALWAYS_SEARCH_USER_PATHS = NO; 561 | CLANG_ENABLE_OBJC_ARC = YES; 562 | CLANG_WARN_BOOL_CONVERSION = YES; 563 | CLANG_WARN_CONSTANT_CONVERSION = YES; 564 | CLANG_WARN_EMPTY_BODY = YES; 565 | CLANG_WARN_ENUM_CONVERSION = YES; 566 | CLANG_WARN_INT_CONVERSION = YES; 567 | CLANG_WARN_UNREACHABLE_CODE = YES; 568 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 569 | COPY_PHASE_STRIP = NO; 570 | ENABLE_STRICT_OBJC_MSGSEND = YES; 571 | GCC_C_LANGUAGE_STANDARD = gnu99; 572 | GCC_DYNAMIC_NO_PIC = NO; 573 | GCC_OPTIMIZATION_LEVEL = 0; 574 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 575 | GCC_PREFIX_HEADER = "KVCMapping/KVCMapping-Prefix.pch"; 576 | GCC_PREPROCESSOR_DEFINITIONS = ( 577 | "DEBUG=1", 578 | "$(inherited)", 579 | ); 580 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 581 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 582 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 583 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 584 | GCC_WARN_UNDECLARED_SELECTOR = YES; 585 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 586 | GCC_WARN_UNUSED_FUNCTION = YES; 587 | GCC_WARN_UNUSED_VARIABLE = YES; 588 | ONLY_ACTIVE_ARCH = YES; 589 | OTHER_LDFLAGS = "-ObjC"; 590 | }; 591 | name = Debug; 592 | }; 593 | 37A80E11150B8CE800B9B6E1 /* Release */ = { 594 | isa = XCBuildConfiguration; 595 | buildSettings = { 596 | ALWAYS_SEARCH_USER_PATHS = NO; 597 | CLANG_ENABLE_OBJC_ARC = YES; 598 | CLANG_WARN_BOOL_CONVERSION = YES; 599 | CLANG_WARN_CONSTANT_CONVERSION = YES; 600 | CLANG_WARN_EMPTY_BODY = YES; 601 | CLANG_WARN_ENUM_CONVERSION = YES; 602 | CLANG_WARN_INT_CONVERSION = YES; 603 | CLANG_WARN_UNREACHABLE_CODE = YES; 604 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 605 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 606 | ENABLE_STRICT_OBJC_MSGSEND = YES; 607 | GCC_C_LANGUAGE_STANDARD = gnu99; 608 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 609 | GCC_PREFIX_HEADER = "KVCMapping/KVCMapping-Prefix.pch"; 610 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 611 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 612 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 613 | GCC_WARN_UNDECLARED_SELECTOR = YES; 614 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 615 | GCC_WARN_UNUSED_FUNCTION = YES; 616 | GCC_WARN_UNUSED_VARIABLE = YES; 617 | OTHER_LDFLAGS = "-ObjC"; 618 | VALIDATE_PRODUCT = YES; 619 | }; 620 | name = Release; 621 | }; 622 | 37A80E13150B8CE800B9B6E1 /* Debug */ = { 623 | isa = XCBuildConfiguration; 624 | buildSettings = { 625 | CODE_SIGN_IDENTITY = "iPhone Developer"; 626 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 627 | PRODUCT_NAME = "$(TARGET_NAME)"; 628 | SDKROOT = iphoneos; 629 | SKIP_INSTALL = YES; 630 | }; 631 | name = Debug; 632 | }; 633 | 37A80E14150B8CE800B9B6E1 /* Release */ = { 634 | isa = XCBuildConfiguration; 635 | buildSettings = { 636 | CODE_SIGN_IDENTITY = "iPhone Developer"; 637 | IPHONEOS_DEPLOYMENT_TARGET = 5.1; 638 | PRODUCT_NAME = "$(TARGET_NAME)"; 639 | SDKROOT = iphoneos; 640 | SKIP_INSTALL = YES; 641 | }; 642 | name = Release; 643 | }; 644 | /* End XCBuildConfiguration section */ 645 | 646 | /* Begin XCConfigurationList section */ 647 | 37235E831662BCCC00F33BF1 /* Build configuration list for PBXNativeTarget "KVCMapping-osx" */ = { 648 | isa = XCConfigurationList; 649 | buildConfigurations = ( 650 | 37235E811662BCCC00F33BF1 /* Debug */, 651 | 37235E821662BCCC00F33BF1 /* Release */, 652 | ); 653 | defaultConfigurationIsVisible = 0; 654 | defaultConfigurationName = Release; 655 | }; 656 | 373EE1261982CE5100062CD9 /* Build configuration list for PBXNativeTarget "KVCMappingTests" */ = { 657 | isa = XCConfigurationList; 658 | buildConfigurations = ( 659 | 373EE1271982CE5100062CD9 /* Debug */, 660 | 373EE1281982CE5100062CD9 /* Release */, 661 | ); 662 | defaultConfigurationIsVisible = 0; 663 | defaultConfigurationName = Release; 664 | }; 665 | 37A80DE8150B8CE800B9B6E1 /* Build configuration list for PBXProject "KVCMapping" */ = { 666 | isa = XCConfigurationList; 667 | buildConfigurations = ( 668 | 37A80E10150B8CE800B9B6E1 /* Debug */, 669 | 37A80E11150B8CE800B9B6E1 /* Release */, 670 | ); 671 | defaultConfigurationIsVisible = 0; 672 | defaultConfigurationName = Release; 673 | }; 674 | 37A80E12150B8CE800B9B6E1 /* Build configuration list for PBXNativeTarget "KVCMapping-ios" */ = { 675 | isa = XCConfigurationList; 676 | buildConfigurations = ( 677 | 37A80E13150B8CE800B9B6E1 /* Debug */, 678 | 37A80E14150B8CE800B9B6E1 /* Release */, 679 | ); 680 | defaultConfigurationIsVisible = 0; 681 | defaultConfigurationName = Release; 682 | }; 683 | /* End XCConfigurationList section */ 684 | }; 685 | rootObject = 37A80DE5150B8CE800B9B6E1 /* Project object */; 686 | } 687 | -------------------------------------------------------------------------------- /KVCMapping.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KVCMapping.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KVCMapping.xcodeproj/xcshareddata/xcschemes/KVCMappingTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntitiesCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntitiesCache.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 15/04/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @class KVCInstancesCache; 12 | 13 | // Entities Cache. 14 | // Entities cache are optionally used when fetching an object (in NSManagedObject+KVCFetching) 15 | // directly or via a relationship (NSManagedObject+KVCRelationship). 16 | // 17 | // An Entities Cache is built before a large Fetch/Set/Set operation, like a parsing. 18 | // It will then be used instead of actual accesses to the CoreData database. 19 | // 20 | // This can provide a significant speedup of operations involving a large number of fetches: as all 21 | // objects are prefetched in a single pass at the beginning, all subsequent access to objects are 22 | // basically free. 23 | 24 | // A collection of Instance Caches 25 | @interface KVCEntitiesCache : NSObject 26 | - (id) initWithInstanceCaches:(NSArray*)instanceCaches; 27 | - (KVCInstancesCache*)instancesCacheForEntity:(NSEntityDescription*)entity; 28 | - (NSSet*)accessedInstances; 29 | - (NSSet*)unaccessedInstances; 30 | @end 31 | 32 | // A simple key-value collection for each Entity Cache. 33 | @interface KVCInstancesCache : NSObject 34 | // Fetches all instances of an entity, and cache it using the specified key. 35 | - (id) initWithContext:(NSManagedObjectContext*)moc entityName:(NSString*)entityName primaryKey:(id)primaryKey; 36 | @property (readonly) NSEntityDescription * entityDescription; 37 | - (id) instanceForKey:(id)key; 38 | - (void)setInstance:(id)instance forKey:(id)key; 39 | // Everytime an instance is accessed from the cache (via instanceForKey:), it's added to this property. 40 | - (NSSet*)accessedInstances; 41 | - (NSSet*)unaccessedInstances; 42 | @end 43 | 44 | #pragma mark - Creation using a ModelMapping 45 | 46 | @interface KVCEntitiesCache (ModelMapping) 47 | // Create instance caches on the specified `moc` for the Entities described in `modelMapping`, 48 | // using only the EntityMappings fro the passed `keys`. 49 | - (id) initWithObjectKeys:(NSArray*)keys inModelMapping:(KVCModelMapping*)modelMapping inContext:(NSManagedObjectContext*)moc; 50 | @end 51 | 52 | @interface KVCInstancesCache (ModelMapping) 53 | // Create an Instance cache on the specifiec context using the entity info (entity name and primary key) from the mapping 54 | - (id) initWithContext:(NSManagedObjectContext*)moc entityMapping:(KVCEntityMapping*)entityMapping; 55 | @end 56 | 57 | #pragma mark - Subscripting 58 | 59 | @interface KVCEntitiesCache (Subscripting) 60 | - (KVCInstancesCache*)objectForKeyedSubscript:(id)key; 61 | @end 62 | 63 | @interface KVCInstancesCache (Subscripting) 64 | - (id)objectForKeyedSubscript:(id)key; 65 | - (void)setObject:(id)obj forKeyedSubscript:(id )key; 66 | @end 67 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntitiesCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntitiesCache.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 15/04/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "KVCEntitiesCache.h" 10 | 11 | #pragma mark KVCEntitiesCache 12 | 13 | @implementation KVCEntitiesCache 14 | { 15 | NSDictionary * _entitiesCache; 16 | } 17 | 18 | - (id) initWithInstanceCaches:(NSArray*)instanceCaches_ 19 | { 20 | self = [super init]; 21 | NSMutableDictionary * dict = NSMutableDictionary.new; 22 | for (KVCInstancesCache * instanceCache in instanceCaches_) { 23 | dict[instanceCache.entityDescription.name] = instanceCache; 24 | } 25 | _entitiesCache = [NSDictionary dictionaryWithDictionary:dict]; 26 | return self; 27 | } 28 | 29 | - (KVCInstancesCache*)instancesCacheForEntity:(NSEntityDescription*)entity 30 | { 31 | return self[entity.name]; 32 | } 33 | 34 | - (NSSet*)accessedInstances 35 | { 36 | return [[NSSet setWithArray:[_entitiesCache allValues]] valueForKeyPath:@"@distinctUnionOfSets.accessedInstances"]; 37 | } 38 | 39 | - (NSSet*)unaccessedInstances 40 | { 41 | return [[NSSet setWithArray:[_entitiesCache allValues]] valueForKeyPath:@"@distinctUnionOfSets.unaccessedInstances"]; 42 | } 43 | 44 | @end 45 | 46 | #pragma mark KVCInstancesCache 47 | 48 | @implementation KVCInstancesCache 49 | { 50 | NSMutableDictionary * _instances; 51 | NSMutableSet * _accessedInstances; 52 | } 53 | 54 | - (id) initWithContext:(NSManagedObjectContext*)moc entityName:(NSString*)entityName primaryKey:(id)primaryKey 55 | { 56 | self = [super init]; 57 | NSEntityDescription * entityDescription = [NSEntityDescription entityForName:entityName inManagedObjectContext:moc]; 58 | NSFetchRequest * frequest = NSFetchRequest.new; 59 | frequest.entity = entityDescription; 60 | frequest.returnsObjectsAsFaults = NO; // Actually load everything. 61 | NSError * error; 62 | NSArray * objects = [moc executeFetchRequest:frequest error:&error]; 63 | NSAssert(error==nil, @"fetch should not fail %@ %@", frequest, error); 64 | NSMutableDictionary * instances = NSMutableDictionary.new; 65 | for (id object in objects) { 66 | instances[[object valueForKey:primaryKey]] = object; 67 | } 68 | _instances = instances; 69 | _entityDescription = entityDescription; 70 | _accessedInstances = NSMutableSet.new; 71 | return self; 72 | } 73 | 74 | - (id) init 75 | { 76 | [self doesNotRecognizeSelector:_cmd]; 77 | return nil; 78 | } 79 | 80 | - (id) instanceForKey:(id)key 81 | { 82 | id instance = _instances[key]; 83 | if(instance) { 84 | [_accessedInstances addObject:instance]; 85 | } 86 | return instance; 87 | } 88 | 89 | - (void)setInstance:(id)instance forKey:(id)key 90 | { 91 | [_accessedInstances addObject:instance]; 92 | _instances[key] = instance; 93 | } 94 | 95 | - (NSSet*)accessedInstances 96 | { 97 | return [NSSet setWithSet:_accessedInstances]; 98 | } 99 | 100 | - (NSSet*)unaccessedInstances 101 | { 102 | if([_accessedInstances count] == [_instances count]) { 103 | return [NSSet set]; 104 | } else { 105 | NSMutableSet * unaccessedInstances = [NSMutableSet setWithArray:[_instances allValues]]; 106 | [unaccessedInstances minusSet:_accessedInstances]; 107 | return [NSSet setWithSet:unaccessedInstances]; 108 | } 109 | } 110 | 111 | @end 112 | 113 | 114 | #pragma mark - Creation using a ModelMapping 115 | 116 | @implementation KVCEntitiesCache (ModelMapping) 117 | - (id) initWithObjectKeys:(NSArray*)keys inModelMapping:(KVCModelMapping*)modelMapping inContext:(NSManagedObjectContext*)moc 118 | { 119 | NSMutableArray * instanceCaches = NSMutableArray.new; 120 | for (NSString * key in keys) { 121 | KVCEntityMapping * entityMapping = [modelMapping entityMappingForKey:key]; 122 | if(entityMapping) { 123 | [instanceCaches addObject:[[KVCInstancesCache alloc] initWithContext:moc 124 | entityMapping:entityMapping]]; 125 | } 126 | } 127 | return [self initWithInstanceCaches:instanceCaches]; 128 | } 129 | @end 130 | 131 | @implementation KVCInstancesCache (ModelMapping) 132 | - (id) initWithContext:(NSManagedObjectContext*)moc entityMapping:(KVCEntityMapping*)entityMapping 133 | { 134 | return [self initWithContext:moc entityName:entityMapping.entityName primaryKey:entityMapping.primaryKey]; 135 | } 136 | @end 137 | 138 | #pragma mark - Subscripting 139 | 140 | @implementation KVCEntitiesCache (Subscripting) 141 | - (id)objectForKeyedSubscript:(id)key 142 | { 143 | return _entitiesCache[key]; 144 | } 145 | @end 146 | 147 | 148 | @implementation KVCInstancesCache (Subscripting) 149 | - (id)objectForKeyedSubscript:(id)key 150 | { 151 | return [self instanceForKey:key]; 152 | } 153 | 154 | - (void)setObject:(id)obj forKeyedSubscript:(id )key 155 | { 156 | [self setInstance:obj forKey:key]; 157 | } 158 | @end 159 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntityMapping+AssignValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMapping+AssignValue.h 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 24/05/13. 6 | // 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @interface KVCKeyMapping (KVCAssignValue) 12 | // Interpret the value and set it to the object, depending of the receiver's settings. 13 | // Base implementation does nothing. 14 | - (void)assignValue:(id)value toObject:(id)object options:(NSDictionary*)options; 15 | 16 | // Obtain the external value from the object for this mapping. 17 | // Base implementation does nothing. 18 | - (id) valueFromObject:(id)object options:(NSDictionary*)options; 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntityMapping+AssignValue.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMapping+AssignValue.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 24/05/13. 6 | // 7 | // 8 | 9 | #import "KVCEntityMapping+AssignValue.h" 10 | #import "NSAttributeDescription+Coercion.h" 11 | #import "KVCMappingOptions.h" 12 | #import "NSObject+KVCMapping.h" 13 | #import "NSObject+KVCCollection.h" 14 | #import "NSManagedObject+KVCRelationship.h" 15 | #import "NSManagedObject+KVCSubobject.h" 16 | 17 | #pragma mark - 18 | 19 | @implementation KVCKeyMapping (KVCAssignValue) 20 | - (void)assignValue:(id)value toObject:(id)object options:(NSDictionary*)options { 21 | [self doesNotRecognizeSelector:_cmd]; 22 | } 23 | - (id) valueFromObject:(id)object options:(NSDictionary*)options { 24 | [self doesNotRecognizeSelector:_cmd]; 25 | return nil; 26 | } 27 | @end 28 | 29 | #pragma mark - 30 | 31 | @implementation KVCPropertyMapping (KVCAssignValue) 32 | - (void)assignValue:(id)value toObject:(id)object options:(NSDictionary*)options 33 | { 34 | if(self.transformer) { 35 | value = [self.transformer transformedValue:value]; 36 | } 37 | 38 | // If the object is a NSManagedObject and the property is a CoreData attribute, 39 | // use the attribute description to convert value, if necessary. 40 | if([[object class] isSubclassOfClass:[NSManagedObject class]]) { 41 | NSAttributeDescription * attributeDesc = [[object entity] attributesByName][self.property]; 42 | if(attributeDesc) { 43 | value = [attributeDesc kvc_coerceValue:value]; 44 | } 45 | } 46 | 47 | [object setValue:value forKey:self.property]; 48 | } 49 | - (id) valueFromObject:(id)object options:(NSDictionary*)options 50 | { 51 | id value = [object valueForKey:self.property]; 52 | 53 | // If the object is a NSManagedObject, fix the NSNumber underlying type to the actual attribute type. 54 | if([[object class] isSubclassOfClass:[NSManagedObject class]]) { 55 | NSAttributeDescription * attributeDesc = [[object entity] attributesByName][self.property]; 56 | if(attributeDesc) { 57 | value = [attributeDesc kvc_fixNumberValueType:value]; 58 | } 59 | } 60 | 61 | id transformedValue = value; 62 | 63 | if(self.transformer) { 64 | if([[self.transformer class] allowsReverseTransformation]) { 65 | transformedValue = [self.transformer reverseTransformedValue:value]; 66 | } else { 67 | return nil; 68 | } 69 | } 70 | return transformedValue ?: [NSNull null]; 71 | } 72 | @end 73 | 74 | #pragma mark - 75 | 76 | @implementation KVCRelationshipMapping (KVCAssignValue) 77 | - (void)assignValue:(id)value toObject:(id)object options:(NSDictionary*)options 78 | { 79 | NSRelationshipDescription * relationshipDesc = [[object entity] relationshipsByName][self.relationship]; 80 | if(!relationshipDesc.isToMany) { 81 | [object kvc_setRelationship:self.relationship toObjectWithValue:value forKey:self.foreignKey options:options]; 82 | } else { 83 | value = [value kvc_embedInCollectionIfNeeded]; 84 | [object kvc_setRelationship:self.relationship toObjectsWithValueIn:value forKey:self.foreignKey options:options]; 85 | } 86 | } 87 | - (id) valueFromObject:(id)object options:(NSDictionary*)options 88 | { 89 | id value = nil; 90 | 91 | NSRelationshipDescription * relationshipDesc = [[object entity] relationshipsByName][self.relationship]; 92 | if(!relationshipDesc.isToMany) { 93 | value = [[object valueForKey:self.relationship] valueForKey:self.foreignKey]; 94 | } else if ([options[KVCIncludeToManyRelationshipsOption] boolValue]) { 95 | if(relationshipDesc.isOrdered) { 96 | value = [[[object valueForKey:self.relationship] array] valueForKey:self.foreignKey]; 97 | } else { 98 | value = [[[object valueForKey:self.relationship] allObjects] valueForKey:self.foreignKey]; 99 | } 100 | } else { 101 | return nil; 102 | } 103 | return value ?: [NSNull null]; 104 | } 105 | @end 106 | 107 | #pragma mark - 108 | 109 | @implementation KVCSubobjectMapping (KVCAssignValue) 110 | - (void)assignValue:(id)value toObject:(id)object options:(NSDictionary*)options 111 | { 112 | NSRelationshipDescription * relationshipDesc = [[object entity] relationshipsByName][self.relationship]; 113 | if(!relationshipDesc.isToMany) { 114 | [object kvc_setRelationship:self.relationship toSubobjectFromValues:value usingMapping:self.mapping options:options]; 115 | } else { 116 | value = [value kvc_embedInCollectionIfNeeded]; 117 | [object kvc_setRelationship:self.relationship toSubobjectsFromValuesCollection:value usingMapping:self.mapping options:options]; 118 | } 119 | } 120 | - (id) valueFromObject:(id)object options:(NSDictionary*)options 121 | { 122 | if(![options[KVCIncludeSubobjectsOption] boolValue]) 123 | return nil; 124 | 125 | id value = nil; 126 | NSRelationshipDescription * relationshipDesc = [[object entity] relationshipsByName][self.relationship]; 127 | 128 | if(!relationshipDesc.isToMany) { 129 | value = [[object valueForKey:self.relationship] kvc_valuesWithEntityMapping:self.mapping options:options]; 130 | } else { 131 | NSMutableArray * result = NSMutableArray.new; 132 | for (id subobject in [object valueForKey:self.relationship]) { 133 | [result addObject:[subobject kvc_valuesWithEntityMapping:self.mapping options:options]]; 134 | } 135 | value = [NSArray arrayWithArray:result]; 136 | } 137 | return value ?: [NSNull null]; 138 | } 139 | @end 140 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntityMapping+Description.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMapping+Description.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 24/05/13. 6 | // 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @implementation KVCKeyMapping (Description) 12 | - (NSString*)description 13 | { 14 | return [self descriptionWithIndent:0]; 15 | } 16 | - (NSString*)descriptionWithIndent:(NSUInteger)indent 17 | { 18 | return [NSString stringWithFormat:@"Mapping key %@",self.key]; 19 | } 20 | @end 21 | 22 | @implementation KVCEntityMapping (Description) 23 | - (NSString*)description 24 | { 25 | return [self descriptionWithIndent:0]; 26 | } 27 | - (NSString*)descriptionWithIndent:(NSUInteger)indent 28 | { 29 | NSMutableString * description = [NSMutableString stringWithFormat:@"Entity Mapping: %p primary key: %@",self, self.primaryKey]; 30 | for (KVCKeyMapping* keyMapping in self.keyMappings) { 31 | [description appendString:@"\n"]; 32 | for (NSUInteger i=0; i1 ? components[1] : nil; 45 | NSDictionary * entityMappingDictionary = [entityDict allValues][0]; 46 | KVCEntityMapping * entityMapping = [[KVCEntityMapping alloc] initWithMappingDictionary:entityMappingDictionary 47 | primaryKey:primaryKey 48 | entityName:entityName]; 49 | if([keys isKindOfClass:[NSArray class]]) { 50 | for (id key in keys) { 51 | dict[key] = entityMapping; 52 | } 53 | } else { 54 | dict[keys] = entityMapping; 55 | } 56 | } 57 | self.entityMappings = [NSDictionary dictionaryWithDictionary:dict]; 58 | 59 | return self; 60 | } 61 | @end 62 | 63 | @implementation KVCEntityMapping (MappingDictionary) 64 | - (id)initWithMappingDictionary:(NSDictionary *)rawEntityMapping_ primaryKey:(NSString*)primaryKey_ entityName:(NSString*)entityName_ 65 | { 66 | NSMutableArray * keyMappings = NSMutableArray.new; 67 | // For each key 68 | if(rawEntityMapping_) { 69 | for (NSString* key_ in rawEntityMapping_) { 70 | id rawMappings_ = rawEntityMapping_[key_]; 71 | if(![rawMappings_ isKindOfClass:[NSArray class]]) { 72 | rawMappings_ = @[rawMappings_]; 73 | } 74 | // For each mapping 75 | for (id rawMapping_ in rawMappings_) { 76 | [keyMappings addObject:[[KVCKeyMapping alloc] initWithRawMapping:rawMapping_ key:key_]]; 77 | } 78 | } 79 | } else { 80 | [keyMappings addObject:[[KVCKeyMapping alloc] initWithRawMapping:primaryKey_ key:primaryKey_]]; 81 | } 82 | return [self initWithKeyMappings:[NSArray arrayWithArray:keyMappings] primaryKey:primaryKey_ entityName:entityName_]; 83 | } 84 | @end 85 | 86 | @implementation KVCKeyMapping 87 | 88 | - (id) initWithKey:(id)key_ 89 | { 90 | self = [super init]; 91 | _key = key_; 92 | return self; 93 | } 94 | 95 | - (id) initWithRawMapping:(id)rawMapping_ key:(id)key_ 96 | { 97 | if([rawMapping_ isKindOfClass:self.class]) { 98 | ((KVCKeyMapping*)rawMapping_)->_key = key_; 99 | return rawMapping_; 100 | } 101 | 102 | Class class = [self.class _mappingClassWithRawMapping:rawMapping_]; 103 | NSParameterAssert([class isSubclassOfClass:self.class] && ![class isEqual:self.class]); 104 | return [[class alloc] initWithRawMapping:rawMapping_ key:key_]; 105 | } 106 | 107 | + (Class) _mappingClassWithRawMapping:(id)rawMapping_ 108 | { 109 | // Parse raw mapping string 110 | if([rawMapping_ isKindOfClass:[NSString class]]) { 111 | 112 | // Relationship 113 | if([rawMapping_ rangeOfString:KVCMapPrimaryKeySeparator].location != NSNotFound) { 114 | return [KVCRelationshipMapping class]; 115 | } 116 | 117 | // Property 118 | return [KVCPropertyMapping class]; 119 | } 120 | 121 | // Subobject 122 | if([rawMapping_ isKindOfClass:[NSDictionary class]]) { 123 | return [KVCSubobjectMapping class]; 124 | } 125 | return Nil; 126 | } 127 | @end 128 | 129 | @implementation KVCPropertyMapping 130 | - (id) initWithRawMapping:(NSString*)mappingString key:(id)key_ 131 | { 132 | self = [super initWithKey:key_]; 133 | NSArray * components = [mappingString componentsSeparatedByString:KVCMapTransformerSeparator]; 134 | if([components count]==2){ 135 | _property = components[1]; 136 | _transformer = [NSValueTransformer valueTransformerForName:components[0]]; 137 | NSParameterAssert(_transformer); 138 | } else { 139 | _property = components[0]; 140 | } 141 | return self; 142 | } 143 | @end 144 | 145 | @implementation KVCRelationshipMapping 146 | - (id) initWithRawMapping:(NSString*)mappingString key:(id)key_ 147 | { 148 | self = [super initWithKey:key_]; 149 | NSArray * components = [mappingString componentsSeparatedByString:KVCMapPrimaryKeySeparator]; 150 | NSParameterAssert([components count]==2); 151 | _relationship = components[0]; 152 | _foreignKey = components[1]; 153 | return self; 154 | } 155 | @end 156 | 157 | @implementation KVCSubobjectMapping 158 | - (id) initWithRawMapping:(NSDictionary*)relationshipDictionary key:(id)key_ 159 | { 160 | NSParameterAssert([relationshipDictionary count]==1); 161 | self = [super initWithKey:key_]; 162 | NSArray * components = [[relationshipDictionary allKeys][0] componentsSeparatedByString:KVCMapPrimaryKeySeparator]; 163 | _relationship = components[0]; 164 | _mapping = [[KVCEntityMapping alloc] initWithMappingDictionary:[relationshipDictionary allValues][0] 165 | primaryKey:[components count]==2?components[1]:nil 166 | entityName:nil]; 167 | return self; 168 | } 169 | @end 170 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntityMapping.h: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMapping.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 06/05/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class KVCEntityMapping, KVCKeyMapping, KVCPropertyMapping, KVCRelationshipMapping, KVCSubobjectMapping; 12 | 13 | #pragma mark - KVCModelMapping 14 | 15 | // Model Mapping 16 | // Maps arbitrary keys to entityMapping 17 | @interface KVCModelMapping : NSObject 18 | @property NSDictionary * entityMappings; 19 | - (KVCEntityMapping*)entityMappingForKey:(id)key; 20 | - (KVCEntityMapping*)entityMappingForEntityName:(NSString*)entityName; 21 | - (NSArray*)keysForEntityName:(NSString*)entityName; 22 | @end 23 | 24 | #pragma mark - KVCEntityMapping 25 | 26 | // maps an external object representation to its internal properties and relationships. 27 | // Each external key is mapped to one or several KVCKeyMappings. 28 | @interface KVCEntityMapping : NSObject 29 | @property NSArray * keyMappings; 30 | @property NSString* entityName; 31 | // init 32 | - (id) initWithKeyMappings:(NSArray*)keyMappings_ primaryKey:(NSString*)primaryKey_ entityName:(NSString*)entityName_; 33 | 34 | // Returns the KVCKeyMappings for this data key. 35 | // 36 | // `key` can be an NSString (if the external object representation is an NSDictionary) 37 | // or an NSNumber (if the external object representation is an NSArray). 38 | - (NSArray*)mappingsForKey:(id)key; 39 | 40 | // All keys in mapping 41 | - (NSArray*)allKeys; 42 | 43 | // The Primary Key of the Entity 44 | // Used in fetch-or-create situation (see kvc_fetchObjectInContext, kvc_importObjects: and KVCEntitiesCache) 45 | @property NSString* primaryKey; 46 | 47 | // Reverse mapping 48 | 49 | // Return the external mappings for a given property or relationship. 50 | - (NSArray*)mappingsTo:(NSString*)propertyOrRelationship; 51 | 52 | // Given the name of a property or relationship, extract the associated value from a collection of `values`. 53 | // 54 | // The collection of `values` is usually a dictionary, but can also be an array. In this case, the key 55 | // in the mapping is expected to be an NSNumber that provides the index of the value in the `values` array. 56 | // 57 | // If the mapping associated to the property defines a value transformer, the returned value 58 | // will be converted using this transformer. 59 | - (id) extractValueFor:(NSString*)propertyOrRelationship fromValues:(id)values; 60 | 61 | @end 62 | 63 | #pragma mark - KVCKeyMapping 64 | 65 | // Abstract Key mapping base class 66 | @interface KVCKeyMapping : NSObject 67 | @property id key; 68 | @end 69 | 70 | #pragma mark - KVCPropertyMapping 71 | 72 | // Key <-> Property mapping, with an optional Value Transformer 73 | 74 | /* 75 | Mapping Dictionary: 76 | 77 | Map to 1 property: 78 | @{ @"externalkey" : @"internalproperty" } 79 | 80 | Map using a value transformer: 81 | @{ @"externalkey" : @"transformername:internalproperty" } 82 | 83 | Map to two properties, using different value transformers: 84 | @{ @"full_name" : @[@"ExtractFirstName:firstName", @"ExtractLastName:lastName"] } 85 | */ 86 | @interface KVCPropertyMapping : KVCKeyMapping 87 | @property NSString * property; 88 | @property NSValueTransformer * transformer; 89 | @end 90 | 91 | #pragma mark - KVCRelationshipMapping 92 | 93 | // Key <-> Relationship mapping. 94 | // In the data, values are mapped to a foreign key in the remote object(s) : 95 | // The mapping works the same for to-one or to-many relationships. 96 | // 97 | // This only makes sense when using NSManagedObject, as we need to find out the remote entity automatically. 98 | 99 | /* 100 | Mapping Dictionary: 101 | 102 | @{ @"externalkey" : @"relationship.foreignkey" } 103 | Given a Company *-. Enployee data model, 104 | This: 105 | @{ @"company" : @"company.identifier" } 106 | can be used to map data of the form 107 | @{ @"first_name": @"John", @"last_name": @"Doe, @"company": @"1234" } 108 | while this: 109 | @{ @"employees" : @"employees.identifier" } 110 | can be used to map data of the form 111 | @{ @"company_name": @"ACME Inc.", @"employees": @[ @"1", @"2", @"27", @"42" ] } 112 | */ 113 | @interface KVCRelationshipMapping : KVCKeyMapping 114 | @property NSString * relationship; 115 | @property NSString * foreignKey; 116 | @end 117 | 118 | // Key <-> Subobject mapping 119 | // In the data, values are mapped to values of subobject, using the subobject mapping dictionary. 120 | // The mapping works the same for to-one or to-many relationships. 121 | // 122 | // This only makes sense when using NSManagedObject, as we need to find out the remote entity automatically. 123 | 124 | /* 125 | Mapping Dictionary: 126 | 127 | @{ @"externalkey" : @"relationship.identifier", mappingDict } 128 | e.g. this: 129 | @{ @"company_name": @"name", @"employees" : @{ @"employees.identifier": @{ @"first_name": @"firstName", @"last_name": @"lastName" } } } 130 | can be used to map data like this: 131 | @{ @"company_name": @"Avengers Inc.", @"employees": @[ @{@"first_name": @"Tony", @"last_name": @"Stark"}, @{ @"first_name": @"Bruce", @"last_name": @"Banner"} ] } 132 | */ 133 | @interface KVCSubobjectMapping : KVCKeyMapping 134 | @property NSString * relationship; 135 | @property KVCEntityMapping * mapping; 136 | @end 137 | -------------------------------------------------------------------------------- /KVCMapping/KVCEntityMapping.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMapping.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 06/05/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | #pragma mark - KVCModelMapping 12 | 13 | @implementation KVCModelMapping 14 | 15 | - (KVCEntityMapping*)entityMappingForKey:(id)key 16 | { 17 | return self.entityMappings[key]; 18 | } 19 | 20 | - (KVCEntityMapping*)entityMappingForEntityName:(NSString*)entityName 21 | { 22 | for (KVCEntityMapping * entityMapping in [self.entityMappings allValues]) { 23 | if ([entityMapping.entityName isEqualToString:entityName]) { 24 | return entityMapping; 25 | } 26 | } 27 | return nil; 28 | } 29 | 30 | - (NSArray*)keysForEntityName:(NSString*)entityName 31 | { 32 | NSMutableArray * keys = NSMutableArray.new; 33 | for (NSString * key in self.entityMappings) { 34 | if ([[self.entityMappings[key] entityName] isEqualToString:entityName]) { 35 | [keys addObject:key]; 36 | } 37 | } 38 | return [NSArray arrayWithArray:keys]; 39 | } 40 | 41 | @end 42 | 43 | #pragma mark - KVCEntityMapping 44 | 45 | @implementation KVCEntityMapping 46 | { 47 | NSDictionary * _mappingsForKey; 48 | NSMutableDictionary * _reverseMappingsTo; 49 | } 50 | 51 | - (id) initWithKeyMappings:(NSArray*)keyMappings_ primaryKey:(NSString*)primaryKey_ entityName:(NSString*)entityName_ 52 | { 53 | self = [super init]; 54 | _keyMappings = keyMappings_; 55 | _primaryKey = primaryKey_; 56 | _entityName = entityName_; 57 | if(_primaryKey) { 58 | NSParameterAssert([[self mappingsTo:_primaryKey] count]>0); 59 | } 60 | 61 | // Group mappings by key for faster lookup 62 | // The resulting _mappingsForKey dictionary has the following form: 63 | // @{ @"foo": @[KVCKeyMapping-to-fooProperty], 64 | // @"bar": @[KVCKeyMapping-to-barProperty, KVCKeyMapping-to-denormalizedBar] } 65 | NSMutableDictionary * mappingsForKey = [NSMutableDictionary dictionaryWithCapacity:[_keyMappings count]]; 66 | for (KVCKeyMapping * mapping in _keyMappings) { 67 | NSMutableArray * mappings = mappingsForKey[mapping.key]; 68 | if (!mappings) { 69 | mappings = [NSMutableArray arrayWithCapacity:1]; 70 | mappingsForKey[mapping.key] = mappings; 71 | } 72 | [mappings addObject:mapping]; 73 | } 74 | _mappingsForKey = mappingsForKey; 75 | 76 | _reverseMappingsTo = NSMutableDictionary.new; 77 | 78 | return self; 79 | } 80 | 81 | - (NSArray*)mappingsForKey:(id)key 82 | { 83 | return _mappingsForKey[key]; 84 | } 85 | 86 | - (NSArray*)allKeys 87 | { 88 | return [self.keyMappings valueForKey:@"key"]; 89 | } 90 | 91 | - (NSArray*)mappingsTo:(NSString*)propertyOrRelationship 92 | { 93 | NSArray * cachedMappingsTo = _reverseMappingsTo[propertyOrRelationship]; 94 | if(cachedMappingsTo==nil) { 95 | NSMutableArray * mappingsTo = NSMutableArray.new; 96 | for (id keyMapping in self.keyMappings) { 97 | if( ([keyMapping respondsToSelector:@selector(property)] && 98 | [[keyMapping property] isEqualToString:propertyOrRelationship] ) 99 | || ([keyMapping respondsToSelector:@selector(relationship)] && 100 | [[keyMapping relationship] isEqualToString:propertyOrRelationship] ) ) 101 | { 102 | [mappingsTo addObject:keyMapping]; 103 | } 104 | } 105 | cachedMappingsTo = [NSArray arrayWithArray:mappingsTo]; 106 | _reverseMappingsTo[propertyOrRelationship] = cachedMappingsTo; 107 | } 108 | return cachedMappingsTo; 109 | } 110 | 111 | - (id) extractValueFor:(NSString*)propertyOrRelationship fromValues:(id)values 112 | { 113 | NSArray * mappings = [self mappingsTo:propertyOrRelationship]; 114 | if([mappings count]==0) { 115 | return nil; 116 | } else { 117 | id keyMapping = mappings[0]; 118 | id key = [keyMapping key]; 119 | id value; 120 | if([values isKindOfClass:[NSDictionary class]]) { 121 | value = values[key]; 122 | } else if ([values isKindOfClass:[NSArray class]]) { 123 | NSUInteger index = [key unsignedIntegerValue]; 124 | if(index < [values count]) { 125 | value = values[index]; 126 | } 127 | } 128 | if([keyMapping respondsToSelector:@selector(transformer)] && [keyMapping transformer]) { 129 | value = [[keyMapping transformer] transformedValue:value]; 130 | } 131 | return value; 132 | } 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /KVCMapping/KVCMapping-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'KVCMapping' target in the 'KVCMapping' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /KVCMapping/KVCMapping.h: -------------------------------------------------------------------------------- 1 | // 2 | // KVCMapping.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 13/05/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "NSObject+KVCMapping.h" 10 | #import "KVCEntityMapping.h" 11 | #import "KVCEntityMapping+AssignValue.h" 12 | #import "KVCEntityMapping+MappingDictionary.h" 13 | #import "KVCMappingOptions.h" 14 | #import "NSAttributeDescription+Coercion.h" 15 | #import "NSManagedObjectContext+KVCMapping.h" 16 | #import "NSManagedObject+KVCRelationship.h" 17 | #import "NSManagedObject+KVCSubobject.h" 18 | #import "NSEntityDescription+KVCFetching.h" 19 | #import "KVCEntitiesCache.h" 20 | 21 | -------------------------------------------------------------------------------- /KVCMapping/KVCMappingOptions.h: -------------------------------------------------------------------------------- 1 | // 2 | // KVCMappingOptions.h 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 16/05/13. 6 | // 7 | // 8 | 9 | // Mapping and fetching options 10 | 11 | // A NSNumber (bool) 12 | // When YES, if no object is found in a fetch or when setting a relationship, 13 | // a new object matching the requirement is created in returned. 14 | extern NSString* const KVCCreateObjectOption; 15 | 16 | // A KVCEntitiesCache 17 | // If an entities cache is passed, no actual fetch requests will be performed. 18 | // Instead, the cache will be looked up for matching objects. 19 | extern NSString* const KVCEntitiesCacheOption; 20 | 21 | 22 | // Reverse Mapping options 23 | 24 | // A NSNumber (bool) 25 | // Include two-many relationships when obtaining values from an object. 26 | // (By default, only the properties and the to-one relationships are returned.) 27 | extern NSString* const KVCIncludeToManyRelationshipsOption; 28 | 29 | // A NSNumber (bool) 30 | // Include subobjects values when obtaining values from an object. 31 | // (By default, only the properties and the to-one relationships are returned.) 32 | extern NSString* const KVCIncludeSubobjectsOption; 33 | -------------------------------------------------------------------------------- /KVCMapping/KVCMappingOptions.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCMappingOptions.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 16/05/13. 6 | // 7 | // 8 | 9 | #import "KVCMappingOptions.h" 10 | 11 | NSString* const KVCCreateObjectOption = @"KVCCreateObjectOption"; 12 | NSString* const KVCEntitiesCacheOption = @"KVCEntitiesCacheOption"; 13 | NSString* const KVCIncludeToManyRelationshipsOption = @"KVCIncludeToManyRelationshipsOption"; 14 | NSString* const KVCIncludeSubobjectsOption = @"KVCIncludeSubobjectsOption"; 15 | -------------------------------------------------------------------------------- /KVCMapping/NSAttributeDescription+Coercion.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributeDescription+Coercion.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 20/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | 9 | @interface NSAttributeDescription (Coercion) 10 | // Converts the passed value, if necessary, to the expected type for the attribute type. 11 | // (i.e. NSStrings to NSNumbers and vice-versa.) 12 | // 13 | // NSDateAttributeType or NSBinaryDataAttributeType can't be converted to automatically. 14 | // 15 | // If `value` is not in the expected type and can't be converted, returns nil. 16 | - (id) kvc_coerceValue:(id)value; 17 | 18 | 19 | // Make sure the underlying CFNumberType matches the attributetype 20 | // (Only makes sense for Number values, obviously) 21 | // 22 | // CoreData may return NSNumbers whose internal type do not match 23 | // the attribute type. (See CFNumberGetType()) 24 | // 25 | // This leads to issues when encoding the data in a format where this matters, 26 | // e.g. in JSON bools should be `true` or `false`, not `0` or `1` 27 | - (id) kvc_fixNumberValueType:(id)value; 28 | @end 29 | -------------------------------------------------------------------------------- /KVCMapping/NSAttributeDescription+Coercion.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributeDescription+Coercion.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 20/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "NSAttributeDescription+Coercion.h" 10 | #import 11 | 12 | @implementation NSAttributeDescription (Coercion) 13 | 14 | - (Class) kvc_expectedClass 15 | { 16 | Class class = objc_getAssociatedObject(self, _cmd); 17 | if(!class){ 18 | class = NSClassFromString(self.attributeValueClassName); 19 | objc_setAssociatedObject(self, _cmd, class, OBJC_ASSOCIATION_ASSIGN); 20 | } 21 | return class; 22 | } 23 | 24 | static NSString * KVCNumberStringValue(NSNumber* num_) 25 | { 26 | char * buf; 27 | NSUInteger len; 28 | switch (CFNumberGetType((CFNumberRef)num_)) { 29 | case kCFNumberSInt8Type: len = asprintf(&buf, "%c", [num_ charValue]); break; 30 | case kCFNumberSInt16Type: len = asprintf(&buf, "%hd", [num_ shortValue]); break; 31 | case kCFNumberSInt32Type: len = asprintf(&buf, "%d", [num_ intValue]); break; 32 | case kCFNumberSInt64Type: len = asprintf(&buf, "%lld", [num_ longLongValue]); break; 33 | case kCFNumberFloat32Type: len = asprintf(&buf, "%g", [num_ floatValue]); break; 34 | case kCFNumberFloat64Type: len = asprintf(&buf, "%g", [num_ doubleValue]); break; 35 | 36 | case kCFNumberCharType: len = asprintf(&buf, "%c", [num_ charValue]); break; 37 | case kCFNumberShortType: len = asprintf(&buf, "%d", [num_ shortValue]); break; 38 | case kCFNumberIntType: len = asprintf(&buf, "%i", [num_ intValue]); break; 39 | case kCFNumberLongType: len = asprintf(&buf, "%ld", [num_ longValue]); break; 40 | case kCFNumberLongLongType: len = asprintf(&buf, "%lld", [num_ longLongValue]); break; 41 | case kCFNumberFloatType: len = asprintf(&buf, "%g", [num_ floatValue]); break; 42 | case kCFNumberDoubleType: len = asprintf(&buf, "%g", [num_ doubleValue]); break; 43 | 44 | case kCFNumberCFIndexType: len = asprintf(&buf, "%ld", [num_ longValue]); break; 45 | case kCFNumberNSIntegerType: len = asprintf(&buf, "%ld", [num_ longValue]); break; 46 | case kCFNumberCGFloatType: len = asprintf(&buf, "%g", [num_ doubleValue]); break; 47 | 48 | default: return [num_ stringValue]; 49 | } 50 | if(buf) { 51 | return [[NSString alloc] initWithBytesNoCopy:buf length:len encoding:NSASCIIStringEncoding freeWhenDone:YES]; 52 | } else { 53 | return [num_ stringValue]; 54 | } 55 | } 56 | 57 | - (id) kvc_coerceValue:(id)value 58 | { 59 | if(nil==value) { 60 | return nil; 61 | } 62 | 63 | if( [value isKindOfClass:[self kvc_expectedClass]]) { 64 | return value; 65 | } 66 | 67 | switch (self.attributeType) 68 | { 69 | // Numbers 70 | /* 71 | Notes : 72 | * Core data has only signed integers, 73 | * There's no "shortValue" in NSString, 74 | * Using intValue always return a 32-bit integer (an int), while integerValue returns an NSInteger, which may be 64-bit. 75 | */ 76 | case NSBooleanAttributeType : 77 | if([value respondsToSelector:@selector(boolValue)]) 78 | return [NSNumber numberWithBool:[value boolValue]]; 79 | return nil; 80 | 81 | case NSInteger16AttributeType : 82 | case NSInteger32AttributeType : 83 | if([value respondsToSelector:@selector(intValue)]) 84 | return [NSNumber numberWithLong:[value intValue]]; 85 | return nil; 86 | 87 | case NSInteger64AttributeType : 88 | if([value respondsToSelector:@selector(longLongValue)]) 89 | return [NSNumber numberWithLongLong:[value longLongValue]]; 90 | return nil; 91 | 92 | case NSDecimalAttributeType : 93 | if([value respondsToSelector:@selector(decimalValue)]) 94 | return [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]]; 95 | return nil; 96 | 97 | case NSDoubleAttributeType : 98 | if([value respondsToSelector:@selector(doubleValue)]) 99 | return [NSNumber numberWithDouble:[value doubleValue]]; 100 | return nil; 101 | 102 | case NSFloatAttributeType : 103 | if([value respondsToSelector:@selector(floatValue)]) 104 | return [NSNumber numberWithFloat:[value floatValue]]; 105 | return nil; 106 | 107 | // NSStrings 108 | // Avoid using stringValue, which uses locale info. We don't need that. 109 | case NSStringAttributeType : 110 | if([value isKindOfClass:[NSNumber class]]) 111 | return KVCNumberStringValue(value); 112 | if([value respondsToSelector:@selector(stringValue)]) 113 | return [value stringValue]; 114 | return nil; 115 | 116 | // Date, Data : 117 | // We can't coerce automatically. 118 | case NSDateAttributeType : 119 | return nil; 120 | case NSBinaryDataAttributeType: 121 | return nil; 122 | 123 | // Default behaviour for these (probably gonna crash later anyway) 124 | case NSUndefinedAttributeType: 125 | case NSObjectIDAttributeType: 126 | case NSTransformableAttributeType: 127 | return value; 128 | default : 129 | return value; 130 | } 131 | } 132 | 133 | - (id) kvc_fixNumberValueType:(id)value 134 | { 135 | if(nil==value || ![value isKindOfClass:[NSNumber class]]) { 136 | return value; 137 | } 138 | 139 | NSParameterAssert([value isKindOfClass:[self kvc_expectedClass]]); 140 | 141 | switch (self.attributeType) 142 | { 143 | // Numbers 144 | case NSBooleanAttributeType : return [NSNumber numberWithBool:[value boolValue]]; 145 | case NSInteger16AttributeType : return [NSNumber numberWithShort:[value shortValue]]; 146 | case NSInteger32AttributeType : return [NSNumber numberWithLong:[value intValue]]; 147 | case NSInteger64AttributeType : return [NSNumber numberWithLongLong:[value longLongValue]]; 148 | case NSFloatAttributeType : return [NSNumber numberWithFloat:[value floatValue]]; 149 | case NSDoubleAttributeType : return [NSNumber numberWithDouble:[value doubleValue]]; 150 | case NSDecimalAttributeType : return [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]]; 151 | default : return value; 152 | } 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /KVCMapping/NSEntityDescription+KVCFetching.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSEntityDescription+KVCFetching.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 20/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | #import "KVCEntityMapping.h" 9 | 10 | @interface NSEntityDescription (KVCFetching) 11 | // Fetch an object of the receiver entity whose value for `key` is equal to the passed `value` 12 | // 13 | // if a `KVCEntitiesCache` is passed in `KVCEntitiesCacheOption`, this method performs no actual Coredata fetch, and only looks up the cache. 14 | // 15 | // If no match is found and `KVCCreateObjectOption` is @YES, 16 | // a new object of the receiver entity is created, 17 | // its value for `key` is set to `value`, 18 | // and it is added to the `KVCEntitiesCache`, if any. 19 | - (id) kvc_fetchObjectInContext:(NSManagedObjectContext*)moc withValue:(id)value forKey:(NSString*)key options:(NSDictionary*)options; 20 | @end 21 | 22 | @interface NSManagedObject (KVCFetching) 23 | // Convenience Method 24 | // 25 | // Grabs the NSEntityDescription for the receiving NSManagedObject specific subclass, 26 | // and fetches a matching object of this entity. 27 | // 28 | // Uses the receiver class to decide what entity to look for. 29 | // In other words, should only be called on NSManagedObject *subclasses*. 30 | + (instancetype) kvc_fetchObjectInContext:(NSManagedObjectContext*)moc withValue:(id)value forKey:(NSString*)key options:(NSDictionary*)options; 31 | @end 32 | -------------------------------------------------------------------------------- /KVCMapping/NSEntityDescription+KVCFetching.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSEntityDescription+KVCFetching.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 20/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "NSEntityDescription+KVCFetching.h" 10 | #import "KVCEntitiesCache.h" 11 | #import "NSAttributeDescription+Coercion.h" 12 | #import "NSObject+KVCMapping.h" 13 | #import "KVCMappingOptions.h" 14 | 15 | @implementation NSEntityDescription (KVCFetching) 16 | 17 | // Find info about key in entity 18 | - (id) kvc_fetchObjectInContext:(NSManagedObjectContext*)moc withValue:(id)value forKey:(NSString*)key options:(NSDictionary*)options 19 | { 20 | NSAttributeDescription * attributeDesc = [self attributesByName][key]; 21 | if(nil==attributeDesc) { 22 | return nil; 23 | } 24 | 25 | #if DEBUG 26 | if(!attributeDesc.isIndexed) { 27 | NSLog(@"%@: fetching a \"%@\" on key %@, which is not indexed.", NSStringFromSelector(_cmd), [self name], key); 28 | } 29 | #endif 30 | 31 | // convert data if necessary 32 | id correctValue = [attributeDesc kvc_coerceValue:value]; 33 | 34 | // Bail out if we could not coerce the value 35 | // (this also prevents the creation of empty objects) 36 | if(!correctValue) 37 | return nil; 38 | 39 | // Search object, in cache if possible 40 | NSManagedObject * obj; 41 | KVCEntitiesCache * entitiesCache = options[KVCEntitiesCacheOption]; 42 | KVCInstancesCache * instancesCache = entitiesCache[self.name]; 43 | // Use cache if available 44 | // If we have an entitiesCache but no instances Cache for this specific entity, 45 | // fallback to regular fetch. 46 | if(entitiesCache && instancesCache!=nil) { 47 | obj = instancesCache[correctValue]; 48 | } else { 49 | // Regular fetch 50 | NSFetchRequest * request = [[NSFetchRequest alloc] initWithEntityName:[self name]]; 51 | request.fetchLimit = 1; 52 | request.returnsObjectsAsFaults = NO; 53 | 54 | // Using a NSComparisonPredicate here is 2-3 times faster than using predicateWithFormat 55 | request.predicate = [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:key] 56 | rightExpression:[NSExpression expressionForConstantValue:correctValue] 57 | modifier:NSDirectPredicateModifier 58 | type:NSEqualToPredicateOperatorType 59 | options:0]; 60 | 61 | NSError * error=nil; 62 | NSArray *result = [moc executeFetchRequest:request error:&error]; 63 | NSAssert(result!=nil, @"Fetch Request %@ failed %@",request, error); 64 | obj = [result lastObject]; 65 | } 66 | 67 | // Not found : create object, if asked. 68 | BOOL createObject = [options[KVCCreateObjectOption] boolValue]; 69 | if(nil==obj && createObject) 70 | { 71 | obj = [self.class insertNewObjectForEntityForName:[self name] inManagedObjectContext:moc]; 72 | [obj setValue:correctValue forKey:key]; 73 | 74 | instancesCache[correctValue] = obj; 75 | } 76 | return obj; 77 | } 78 | 79 | @end 80 | 81 | #pragma mark - 82 | 83 | @implementation NSManagedObject (KVCFetching) 84 | 85 | // Finds the entity in the passed context's model whose object class is the receiver, or a superclass of the receiver. 86 | // Obviously, it's to be called in subclasses. 87 | // 88 | // Incidentally, this method is overridden in mogenerator's boilerplate code. 89 | // When using mogenerator, this specific NSManagedObject implementation will not be used. 90 | + (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)context 91 | { 92 | NSManagedObjectModel * model = context.persistentStoreCoordinator.managedObjectModel; 93 | for (NSEntityDescription * entity in [model entities]) 94 | { 95 | Class entityClass = NSClassFromString([entity managedObjectClassName]); 96 | if ( ! [entityClass isEqual:[NSManagedObject class]] && [self isSubclassOfClass:entityClass]) 97 | { 98 | return entity; 99 | } 100 | } 101 | return nil; 102 | } 103 | 104 | + (instancetype) kvc_fetchObjectInContext:(NSManagedObjectContext*)moc withValue:(id)value forKey:(NSString*)key options:(NSDictionary*)options 105 | { 106 | NSEntityDescription * entity = [self entityInManagedObjectContext:moc]; 107 | return [entity kvc_fetchObjectInContext:moc withValue:value forKey:key options:options]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObject+KVCRelationship.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+KVCRelationship.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 19/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @interface NSManagedObject (KVCRelationship) 12 | 13 | // 14 | // Fetch an existing object (or create it) and set it as the destination of a relationship. 15 | - (void)kvc_setRelationship:(NSString*)relationshipName toObjectWithValue:(id)value 16 | forKey:(NSString*)key options:(NSDictionary*)options; 17 | // To-many relationship variant : valueCollection is an array or set of values. 18 | - (void)kvc_setRelationship:(NSString*)relationshipName toObjectsWithValueIn:(id)valueCollection 19 | forKey:(NSString*)key options:(NSDictionary*)options; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObject+KVCRelationship.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+KVCRelationship.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas Bouilleaud - Capitaine Train on 19/09/12. 6 | // Copyright (c) 2012 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "NSManagedObject+KVCRelationship.h" 10 | #import "NSEntityDescription+KVCFetching.h" 11 | #import "NSObject+KVCCollection.h" 12 | 13 | @implementation NSManagedObject (KVCRelationship) 14 | 15 | - (void)kvc_setRelationship:(NSString*)relationshipName toObjectWithValue:(id)value 16 | forKey:(NSString*)key options:(NSDictionary*)options 17 | { 18 | NSRelationshipDescription * relationshipDesc = [[self entity] relationshipsByName][relationshipName]; 19 | 20 | NSParameterAssert(!relationshipDesc.isToMany); 21 | 22 | NSEntityDescription * destinationEntity = [relationshipDesc destinationEntity]; 23 | if(nil==destinationEntity) { 24 | return; 25 | } 26 | 27 | id destinationObject = [destinationEntity kvc_fetchObjectInContext:self.managedObjectContext withValue:value forKey:key options:options]; 28 | [self setValue:destinationObject forKey:relationshipName]; 29 | } 30 | 31 | - (void)kvc_setRelationship:(NSString*)relationshipName toObjectsWithValueIn:(id)valueCollection 32 | forKey:(NSString*)key options:(NSDictionary*)options 33 | { 34 | NSRelationshipDescription * relationshipDesc = [[self entity] relationshipsByName][relationshipName]; 35 | 36 | NSParameterAssert([valueCollection kvc_isCollection]); 37 | NSParameterAssert(relationshipDesc.isToMany); 38 | 39 | NSEntityDescription * destinationEntity = [relationshipDesc destinationEntity]; 40 | if(nil==destinationEntity) { 41 | return; 42 | } 43 | 44 | id destinationObjects = relationshipDesc.isOrdered ? NSMutableOrderedSet.new : NSMutableSet.new; 45 | for (id value in valueCollection) { 46 | id destinationObject = [destinationEntity kvc_fetchObjectInContext:self.managedObjectContext withValue:value forKey:key options:options]; 47 | [destinationObjects addObject:destinationObject]; 48 | } 49 | [self setValue:destinationObjects forKey:relationshipName]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObject+KVCSubobject.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+KVCSubobject.h 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 24/05/13. 6 | // 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @interface NSManagedObject (KVCSubobject) 12 | 13 | // Fetch of create an object, set its values, and set it as the destination of a relationship. 14 | // 15 | // If entityMapping has a primary key, an existing object will be searched first. 16 | // Otherwise, a new object will be created, regardless of KVCCreateObjectOption. 17 | - (void)kvc_setRelationship:(NSString*)relationshipName toSubobjectFromValues:(id)values 18 | usingMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 19 | // To-many relationship variant : valuesCollection is a collection of object values. (e.g. an Array of Dictionaries) 20 | - (void)kvc_setRelationship:(NSString*)relationshipName toSubobjectsFromValuesCollection:(id)valuesCollection 21 | usingMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObject+KVCSubobject.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+KVCSubobject.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas @ Capitaine Train on 24/05/13. 6 | // 7 | // 8 | 9 | #import "NSManagedObject+KVCSubobject.h" 10 | #import "NSEntityDescription+KVCFetching.h" 11 | #import "NSObject+KVCMapping.h" 12 | #import "NSObject+KVCCollection.h" 13 | #import "KVCEntityMapping.h" 14 | 15 | @implementation NSManagedObject (KVCSubobject) 16 | 17 | - (void)kvc_setRelationship:(NSString*)relationshipName toSubobjectFromValues:(id)values 18 | usingMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 19 | { 20 | NSRelationshipDescription * relationshipDesc = self.entity.relationshipsByName[relationshipName]; 21 | 22 | NSParameterAssert(!relationshipDesc.isToMany); 23 | 24 | NSEntityDescription * destinationEntity = relationshipDesc.destinationEntity; 25 | if(nil==destinationEntity) { 26 | return; 27 | } 28 | 29 | id destinationObject; 30 | if(entityMapping.primaryKey) { 31 | id primaryValue = [entityMapping extractValueFor:entityMapping.primaryKey fromValues:values]; 32 | destinationObject = [destinationEntity kvc_fetchObjectInContext:self.managedObjectContext withValue:primaryValue forKey:entityMapping.primaryKey options:options]; 33 | } else { 34 | // Alway create subobjects with no primarykey 35 | destinationObject = [NSEntityDescription insertNewObjectForEntityForName:destinationEntity.name inManagedObjectContext:self.managedObjectContext]; 36 | } 37 | // set other values 38 | [destinationObject kvc_setValues:values withEntityMapping:entityMapping options:options]; 39 | 40 | [self setValue:destinationObject forKey:relationshipName]; 41 | } 42 | 43 | - (void)kvc_setRelationship:(NSString*)relationshipName toSubobjectsFromValuesCollection:(id)valuesCollection 44 | usingMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 45 | { 46 | NSRelationshipDescription * relationshipDesc = self.entity.relationshipsByName[relationshipName]; 47 | 48 | NSParameterAssert([valuesCollection kvc_isCollection]); 49 | NSParameterAssert(relationshipDesc.isToMany); 50 | 51 | NSEntityDescription * destinationEntity = relationshipDesc.destinationEntity; 52 | if(nil==destinationEntity) { 53 | return; 54 | } 55 | 56 | id destinationObjects = relationshipDesc.isOrdered ? NSMutableOrderedSet.new : NSMutableSet.new; 57 | for (id values in valuesCollection) { 58 | id destinationObject; 59 | if(entityMapping.primaryKey) { 60 | id primaryValue = [entityMapping extractValueFor:entityMapping.primaryKey fromValues:values]; 61 | destinationObject = [destinationEntity kvc_fetchObjectInContext:self.managedObjectContext withValue:primaryValue forKey:entityMapping.primaryKey options:options]; 62 | } else { 63 | // Alway create subobjects with no primarykey 64 | destinationObject = [NSEntityDescription insertNewObjectForEntityForName:destinationEntity.name inManagedObjectContext:self.managedObjectContext]; 65 | } 66 | // set other values 67 | [destinationObject kvc_setValues:values withEntityMapping:entityMapping options:options]; 68 | 69 | [destinationObjects addObject:destinationObject]; 70 | } 71 | [self setValue:destinationObjects forKey:relationshipName]; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObjectContext+KVCMapping.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+KVCMapping.h 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 15/05/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @interface NSManagedObjectContext (KVCMapping) 12 | 13 | // Create or update the values for the object represented in values in the receiver context 14 | // using `entityMapping` to map the values to the entity properties and relationships. 15 | // 16 | // `values` is the representation of a single NSManagedObject. 17 | // If entityMapping specifies a primary key, search for a matching existing object first. 18 | // 19 | // See also `KVCEntitiesCacheOption` and `KVCCreateObjectOption` 20 | - (NSManagedObject *)kvc_importObject:(NSDictionary*)values 21 | withEntityMapping:(KVCEntityMapping*)entityMapping 22 | options:(NSDictionary*)options; 23 | 24 | // Create or update objects from the passed objects values in the receiver context, 25 | // mapping to entities using the modelMapping. 26 | // 27 | // `objectsValues` is a dictionary whoses keys match the keys in modelMapping. 28 | // 29 | // Return a debug help dictionary containing the imported values for each created/updated managedObjectID. 30 | - (NSDictionary*)kvc_importObjects:(NSDictionary *)objectsValues 31 | withModelMapping:(KVCModelMapping *)modelMapping 32 | options:(NSDictionary *)options; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /KVCMapping/NSManagedObjectContext+KVCMapping.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+KVCMapping.m 3 | // CapitaineTrain 4 | // 5 | // Created by Nicolas @ Capitaine Train on 15/05/13. 6 | // Copyright (c) 2013 Capitaine Train. All rights reserved. 7 | // 8 | 9 | #import "NSManagedObjectContext+KVCMapping.h" 10 | #import "NSObject+KVCMapping.h" 11 | #import "NSEntityDescription+KVCFetching.h" 12 | 13 | @implementation NSManagedObjectContext (KVCMapping) 14 | 15 | - (NSManagedObject *)kvc_importObject:(NSDictionary*)values 16 | withEntityMapping:(KVCEntityMapping*)entityMapping 17 | options:(NSDictionary*)options 18 | { 19 | id primaryValue = [entityMapping extractValueFor:entityMapping.primaryKey fromValues:values]; 20 | NSEntityDescription * entityDescription = [self.persistentStoreCoordinator.managedObjectModel entitiesByName][entityMapping.entityName]; 21 | 22 | // Retrieve the object using the value of its primary key. 23 | // If the object cannot be found and KVCCreateObjectOption is specified, a new managed object is created. 24 | id object = [entityDescription kvc_fetchObjectInContext:self withValue:primaryValue forKey:entityMapping.primaryKey options:options]; 25 | 26 | [object kvc_setValues:values withEntityMapping:entityMapping options:options]; 27 | return object; 28 | } 29 | 30 | - (NSDictionary*)kvc_importObjects:(NSDictionary *)objectsValues 31 | withModelMapping:(KVCModelMapping *)modelMapping 32 | options:(NSDictionary *)options 33 | { 34 | NSMutableDictionary *parsedObjectsInfo = NSMutableDictionary.new; 35 | 36 | for (NSString * key in objectsValues) { 37 | KVCEntityMapping * entityMapping = [modelMapping entityMappingForKey:key]; 38 | 39 | id valueForClass = objectsValues[key]; 40 | 41 | NSDictionary *dictionaries = ([valueForClass isKindOfClass:[NSArray class]] ? valueForClass : 42 | [valueForClass isKindOfClass:[NSDictionary class]] ? [NSArray arrayWithObject:valueForClass] : 43 | nil); 44 | 45 | for (NSDictionary * objectDict in dictionaries) { 46 | NSManagedObject * object = [self kvc_importObject:objectDict withEntityMapping:entityMapping options:options]; 47 | if(object) { 48 | parsedObjectsInfo[object.objectID] = objectDict; 49 | } 50 | } 51 | } 52 | 53 | return [NSDictionary dictionaryWithDictionary:parsedObjectsInfo]; 54 | } 55 | 56 | @end 57 | 58 | -------------------------------------------------------------------------------- /KVCMapping/NSObject+KVCCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCCollection.h 3 | // KVCMapping 4 | // 5 | // Created by Pierre de La Morinerie on 24/05/13. 6 | // 7 | // 8 | 9 | @interface NSObject (KVCCollection) 10 | - (BOOL) kvc_isCollection; 11 | - (id) kvc_embedInCollectionIfNeeded; 12 | @end 13 | -------------------------------------------------------------------------------- /KVCMapping/NSObject+KVCCollection.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCCollection.m 3 | // KVCMapping 4 | // 5 | // Created by Pierre de La Morinerie on 24/05/13. 6 | // 7 | // 8 | 9 | #import "NSObject+KVCCollection.h" 10 | 11 | @implementation NSObject (KVCCollection) 12 | - (BOOL) kvc_isCollection { 13 | return [self isKindOfClass:[NSArray class]] 14 | || [self isKindOfClass:[NSSet class]] 15 | || [self isKindOfClass:[NSOrderedSet class]]; 16 | } 17 | - (id) kvc_embedInCollectionIfNeeded { 18 | return [self kvc_isCollection] ? self : @[ self ]; 19 | } 20 | @end 21 | -------------------------------------------------------------------------------- /KVCMapping/NSObject+KVCMapping.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCMapping.h 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 18/06/11. 6 | // Copyright 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | 9 | #import "KVCEntityMapping.h" 10 | 11 | @interface NSObject (KVCMapping) 12 | 13 | #pragma mark - Forward Mapping 14 | 15 | // Set a `value` for a given `wantedKey` of the receiver, using the `entityMapping` to map the value to a real key. 16 | // * a single `wantedKey` may map to several actual properties or relationships in the receiver. (See KVCKeyMapping) 17 | // * `wantedKey` can be an NSString or an NSNumber. (See KVCEntityMapping) 18 | // * `value` should be of the expected type for the KVCKeyMapping 19 | // 20 | // If the receiver is an NSManagedObject, this can set relationships and create subobjects. 21 | // 22 | // If the receiver is an NSManagedObject, attempt to automatically coerce the data to the expected type of the property. 23 | // 24 | // Does nothing if no mapping is found, or if the value can't be converted to the expected type. 25 | - (void)kvc_setValue:(id)value forKey:(id)wantedKey withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 26 | 27 | // Batch Method. 28 | // Calls - kvc_setValue:forKey:withEntityMapping: repeatedly with the passed `values`. 29 | // 30 | // `values` can be an NSDictionary or an NSArray; 31 | // If `values` is a dictionary, its keys are used as the `wantedKey`s. 32 | // If `values` is an array, its indexes, as NSNumbers, are used as the `wantedKey`s. 33 | - (void)kvc_setValues:(id)values withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 34 | 35 | // Convenience variants: 36 | // Automatically create a KVCEntityMapping with `kvcMappingDictionary` 37 | - (void)kvc_setValue:(id)value forKey:(NSString*)wantedKey withMappingDictionary:(NSDictionary*)kvcMappingDictionary options:(NSDictionary*)options; 38 | - (void)kvc_setValues:(id)values withMappingDictionary:(NSDictionary*)kvcMappingDictionary options:(NSDictionary*)options; 39 | 40 | #pragma mark - Reverse Mapping 41 | 42 | // obtain the value 43 | // 44 | // If there are several mappings for this key, only the first one is used. 45 | // KVCPropertyMappings using transformers must allow reverse transformations. 46 | // Otherwise, the returned value is nil. 47 | - (id) kvc_valueForKey:(id)key withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 48 | 49 | // Batch Method. 50 | // Calls - kvc_valueForKey:withEntityMapping: repeatedly for each key in the mapping 51 | - (id) kvc_valuesWithEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options; 52 | 53 | // Convenience variants: 54 | // Automatically create a KVCEntityMapping with `kvcMappingDictionary` 55 | - (id) kvc_valueForKey:(id)key withMappingDictionary:(NSDictionary*)mappingDictionary options:(NSDictionary*)options; 56 | - (id) kvc_valuesWithMappingDictionary:(NSDictionary*)mappingDictionary options:(NSDictionary*)options; 57 | 58 | @end 59 | 60 | -------------------------------------------------------------------------------- /KVCMapping/NSObject+KVCMapping.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCMapping.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 18/06/11. 6 | // Copyright 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | 9 | #import "NSObject+KVCMapping.h" 10 | #import "KVCEntityMapping+MappingDictionary.h" 11 | #import "KVCEntityMapping+AssignValue.h" 12 | 13 | @implementation NSObject (KVCMapping) 14 | 15 | // Forward mapping 16 | - (void)kvc_setValues:(id)values withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 17 | { 18 | NSParameterAssert([values isKindOfClass:[NSDictionary class]] || [values isKindOfClass:[NSArray class]]); 19 | if([values isKindOfClass:[NSDictionary class]]) { 20 | // Values is a dictionary : just iterate on its keys. 21 | for (NSString * wantedKey in values) { 22 | [self kvc_setValue:values[wantedKey] forKey:wantedKey withEntityMapping:entityMapping options:options]; 23 | } 24 | } else if([values isKindOfClass:[NSArray class]]) { 25 | // Values is an nsarray : use the index as the key 26 | [values enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { 27 | NSNumber * wantedKey = @(index); 28 | [self kvc_setValue:value forKey:wantedKey withEntityMapping:entityMapping options:options]; 29 | }]; 30 | } 31 | } 32 | 33 | - (void)kvc_setValue:(id)value forKey:(id)wantedKey withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 34 | { 35 | // Find the mappings to use for this key (as a source key can be mapped to several destination properties). 36 | NSArray* keyMappings = [entityMapping mappingsForKey:wantedKey]; 37 | 38 | for (KVCKeyMapping * keyMapping in keyMappings){ 39 | [keyMapping assignValue:value toObject:self options:options]; 40 | } 41 | } 42 | 43 | // Convenience Methods 44 | - (void)kvc_setValues:(id)values withMappingDictionary:(NSDictionary*)mappingDict options:(NSDictionary*)options 45 | { 46 | [self kvc_setValues:values withEntityMapping:[[KVCEntityMapping alloc] initWithMappingDictionary:mappingDict primaryKey:nil entityName:nil] options:options]; 47 | } 48 | 49 | - (void)kvc_setValue:(id)value forKey:(id)wantedKey withMappingDictionary:(NSDictionary*)mappingDict options:(NSDictionary*)options 50 | { 51 | [self kvc_setValue:value forKey:wantedKey withEntityMapping:[[KVCEntityMapping alloc] initWithMappingDictionary:mappingDict primaryKey:nil entityName:nil] options:options]; 52 | } 53 | 54 | // Reverse Mapping 55 | - (id) kvc_valueForKey:(id)key withEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 56 | { 57 | NSArray * mappings = [entityMapping mappingsForKey:key]; 58 | if([mappings count]) { 59 | // Only use the first mapping in reverse mapping 60 | return [mappings[0] valueFromObject:self options:options]; 61 | } else { 62 | return nil; 63 | } 64 | } 65 | 66 | - (id) kvc_valuesWithEntityMapping:(KVCEntityMapping*)entityMapping options:(NSDictionary*)options 67 | { 68 | id values = NSMutableDictionary.new; 69 | for (id key in [entityMapping allKeys]) { 70 | id value = [self kvc_valueForKey:key withEntityMapping:entityMapping options:options]; 71 | if(value) { 72 | values[key] = value; 73 | } 74 | } 75 | // Convert to an NSArray if all the keys are NSNumbers 76 | NSArray * keysClasses = [[values allKeys] valueForKeyPath:@"@distinctUnionOfObjects.class"]; 77 | if([keysClasses count]==1 && [keysClasses[0] isSubclassOfClass:[NSNumber class]]) 78 | { 79 | NSMutableArray * valuesArray = NSMutableArray.new; 80 | for (NSUInteger i=0; i<[values count]; i++) { 81 | [valuesArray addObject:values[@(i)]]; 82 | } 83 | values = valuesArray; 84 | } 85 | return values; 86 | } 87 | 88 | // Convenience Methods 89 | - (id) kvc_valuesWithMappingDictionary:(NSDictionary*)mappingDict options:(NSDictionary*)options 90 | { 91 | return [self kvc_valuesWithEntityMapping:[[KVCEntityMapping alloc] initWithMappingDictionary:mappingDict primaryKey:nil entityName:nil] options:options]; 92 | } 93 | 94 | - (id) kvc_valueForKey:(id)key withMappingDictionary:(NSDictionary*)mappingDict options:(NSDictionary*)options 95 | { 96 | return [self kvc_valueForKey:key withEntityMapping:[[KVCEntityMapping alloc] initWithMappingDictionary:mappingDict primaryKey:nil entityName:nil] options:options]; 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2013 Nicolas Bouilleaud 2 | Copyright © 2014 Capitaine Train 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | **KVC Mapping** is the automatic translation of external data to your internal model objects. It's useful when importing data, e.g. JSON from a webservice, into your CoreData backend. 2 | 3 | **KVC Mapping** features 4 | 5 | * Mapping the keys of the external representation to the property names of your model, 6 | * Including mapping of the same key to several properties in the same entity. 7 | * Mapping the keys in the external representation to relationships between your model's entities, 8 | * A conversion mechanism: 9 | * using automatic coercion for trivial cases (e.g. strings to numbers) 10 | * configurable NSValueTransformers for more complex cases. 11 | * Reverse mapping of objects 12 | 13 | **KVC Mapping** does not (yet) support : 14 | * mapping subdictionaries in the imported data, 15 | * mapping several keys of the imported data to a single property of the model. 16 | 17 | KVC Mapping 18 | ----------- 19 | 20 | Let’s say you have a datamodel, representing people, with a name, surname, and birthdate. The corresponding Objective-C class would look like : 21 | 22 | @interface Person : NSObject 23 | @property NSString * name; 24 | @property NSString * surname; 25 | @property NSString * birthdate; 26 | @property BOOL likesCoffee; 27 | @end 28 | 29 | You also happen to have a source for your data, for example a webservice providing JSON data. Unfortunately, the JSON data looks like : 30 | 31 | [ 32 | { 33 | name1:"John", 34 | name2:"Doe", 35 | bday:"2001/01/01" 36 | caffeine:1 37 | }, 38 | { 39 | name1:"Ann", 40 | name2:"Onymous", 41 | bday:"1981/10/23" 42 | caffeine:1 43 | } 44 | ] 45 | 46 | Unfortunately, the JSON uses different keys for the "name", "surname", and "birthdate". 47 | Now you could go through your JSON array, and for every object compare the key string and assign it to the right property, but that looks a bit tedious. 48 | 49 | **The point of KVCMapping is to declare a mapping between the keys used in your models, and the keys used in the data you're importing.** 50 | 51 | In the above example, a simple dictionary : 52 | 53 | { 54 | name1 = name; 55 | name2 = surname; 56 | caffeine = likesCoffee; 57 | } 58 | 59 | would be enough to parse the name and surname. You would then write : 60 | 61 | NSDictionary * mapping = @{ 62 | @"name1":@"name", 63 | @"name2":@"surname", 64 | @"caffeine":@"likesCoffee" 65 | }; 66 | 67 | for( NSDictionary * personAsDictionary in arrayFromJSON) 68 | { 69 | Person * person = ...; // create the data object, for example in CoreData 70 | [person kvc_setValues:personAsDictionary withMappingDictionary:mapping options:0]; 71 | } 72 | 73 | Voilà ! 74 | 75 | Value Transformers 76 | ------------------ 77 | 78 | But what about `birthdate`? `NSDate`s are a little tougher to parse, because there's typically no "date" object in raw data. Dates are typically saved as strings, using a variant of [a standard encoding](http://fr.wikipedia.org/wiki/ISO_8601). Unfortunately, there's no safe way to determine the exact encoding based only on the data. We have to give a hint to the parser. 79 | 80 | In Objective-C, dates are converted to and from strings using NSDateFormatter. Another useful class here is NSValueTransformer, which is used to convert abritrary data from one representation to another. **In KVCMapping, value transformers can be specified by name, right in the mapping dictionary.** 81 | 82 | Let's do this for the above example : 83 | 84 | // Create a value transformer with the date formatter 85 | // I'm using [Mattt’s excellent TransformerKit](https://github.com/mattt/TransformerKit) 86 | // for block-based value-transformers : 87 | [NSValueTransformer registerValueTransformerWithName:@"ISO8601StringToDate" 88 | transformedValueClass:[NSDate class] 89 | returningTransformedValueWithBlock:^id(id value) { 90 | // Setup a date formatter for our external date representation 91 | // In the real world, you probably want to specify locale and cache the date formatter. 92 | NSDateFormatter * dateFormatter = NSDateFormatter.new; 93 | [dateFormatter setDateFormat:@"yyyy/MM/dd"]; 94 | return [dateFormatter dateFromString:value] 95 | }]; 96 | 97 | The full mapping dictionary is : 98 | 99 | NSDictionary * mapping = @{ 100 | @"name1":@"name", 101 | @"name2":@"surname", 102 | @"caffeine":@"likesCoffee", 103 | @"bday":@"ISO8601StringToDate:birthdate" 104 | }; 105 | 106 | Automatic conversions 107 | --------------------- 108 | 109 | ## Type coercion 110 | 111 | Key-Value Coding supports, to some degree, automatic conversion [between objects and scalars](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/KeyValueCoding/Articles/DataTypes.html), but there's nothing in the Objective-C runtime to prevent you from assigning an `NSNumber` to an `NSString` property, or any other object. 112 | 113 | In CoreData, `NSManagedObject`s know a lot more about their own properties, using `NSAttributeDescription`. 114 | 115 | When used with NSManagedObjects, KVCMapping automatically converts objects to the wanted type: 116 | 117 | * `NSNumber`s are converted to `NSString`s, as decimals. 118 | * `NSString`s are converted to `NSNumber`s using `- boolValue`, `- intValue`, `- longlongValue`, `- floatValue`, `- doubleValue`, or to `NSDecimalNumber`s depending of the expected attribute type. 119 | 120 | ## Automatic collections 121 | 122 | When attempting to set a single object to a to-many relationship, KVCMapping automatically boxes the single-object in a one-item collection. 123 | 124 | # Code 125 | 126 | KVCMapping is a multi-layered API, but each level is useful in itself. 127 | 128 | The foremost API are the `kvc_setValue(s):withMapping:` methods on `NSObject` (`NSObject+KVCMapping.h|m`). They take external value(s), and use mapping info to assign it to their receiver. The mapping iself is a `KVCEntityMapping`, typically created from an NSDictionary using a (relatively) friendly syntax. (`KVCEntityMapping+MappingDictionary.h|m`) 129 | 130 | A `KVCEntityMapping` describes how to map external values to an internal model class. It’s composed of several `KVCKeyMapping`s, for each specific type of mapping: property (`KVCPropertyMapping`), relationship (`KVCRelationshipMapping`) or subobject (`KVCSubobjectMapping`). (`KVCEntityMapping.h|m`). 131 | 132 | The exact mechanism used when mapping a value to and from its external representation is in `KVCEntityMapping+AssignValue.h|m`. For NSManagedObjects, it uses entity description to do the mapping, and coercion. (`NSAttributeDescription+Coercion.h|m`, `NSManagedObject+KVCRelationship.h|m`, `NSManagedObject+KVCSubobject.h|m`). `KVCFetching` is used when setting a relationship to an existing object in a CoreData context. (`NSEntityDescription+KVCFetching.h|m`). 133 | 134 | Several `KVCEntityMapping` are typically needed to map a complete data model to and from its external representation: that’s what `KVCModelMapping` is for (`KVCEntityMapping.h|m`). It’s used when importing batch data in CoreData context (`NSManagedObjectContext+KVCMapping.h|m`). It optionally uses a `KVCEntitiesCache` (`KVCEntitiesCache.h|m`) to avoid numerous fetch requests. 135 | 136 | # Mapping types 137 | 138 | * A value mapping is identifier by its key in the external data 139 | 140 | * Property Mapping 141 | -> input is a value 142 | -> an internal property (identified by its name), 143 | -> optionally using a value transformer 144 | 145 | * Relationship Mapping 146 | -> input is a value, used as a key for the relationship. 147 | -> uses a relationship (identified by its name) 148 | -> and the foreignKey to use to fetch this object. 149 | 150 | * Subobject Mapping 151 | -> input is a dictionary (or array), an external representation of the subobject. 152 | -> uses a relationship (identified by its name) 153 | -> and an entity mapping for the subobject. 154 | 155 | * KVCMapping can map external representations from **Dictionary and Arrays** 156 | -> For dictionaries (the usual case), mapping keys are strings 157 | -> For arrays, mapping keys are numbers (as indexes) 158 | 159 | * An EntityMapping can map the same external key to several internal properties or relationships. 160 | * An EntityMapping can map several external keys to the same internal property or relationship. 161 | 162 | # Reverse Mapping 163 | 164 | * When reverse mapping from internal objects to external data, only the first mapping is used for each key. 165 | -------------------------------------------------------------------------------- /Tests/KVCEntitiesCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntitiesCacheTests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "KVCEntitiesCache.h" 10 | #import "NSManagedObject+KVCRelationship.h" 11 | 12 | /****************************************************************************/ 13 | #pragma mark Fetch Tests 14 | 15 | @interface KVCEntitiesCacheTests : XCTestCase 16 | @end 17 | 18 | @implementation KVCEntitiesCacheTests 19 | { 20 | @protected 21 | NSManagedObjectContext * moc; 22 | } 23 | 24 | - (void)setUp 25 | { 26 | [super setUp]; 27 | 28 | moc = NSManagedObjectContext.new; 29 | moc.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: 30 | [[NSManagedObjectModel alloc] initWithContentsOfURL: 31 | [[NSBundle bundleForClass:self.class] URLForResource:@"NSManagedObject_KVCMapping_Tests" 32 | withExtension:@"mom"]]]; 33 | 34 | } 35 | 36 | - (void)testCachePresenceOfEntities 37 | { 38 | // Given 39 | // Create an A instance 40 | NSManagedObject * a1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 41 | [a1 setValue:@"value1" forKey:@"attributeInA"]; 42 | // Create an B instance 43 | NSManagedObject * b1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 44 | [b1 setValue:@"value1" forKey:@"attributeInB"]; 45 | 46 | // When 47 | // Create a cache of A instances 48 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 49 | @[[[KVCInstancesCache alloc] initWithContext:moc 50 | entityName:@"TestRelatedEntityA" 51 | primaryKey:@"attributeInA"]]]; 52 | 53 | // Then 54 | XCTAssertEqualObjects(cache[@"TestRelatedEntityA"][@"value1"], a1, @"object a1 should be in cache"); 55 | XCTAssertNil(cache[@"TestRelatedEntityB"][@"value1"], @"object b1 should not be in cache"); 56 | 57 | // When 58 | // Create a new A instance 59 | NSManagedObject * a2 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 60 | [a2 setValue:@"value2" forKey:@"attributeInA"]; 61 | 62 | // Then 63 | // It should not be in the cache 64 | XCTAssertNil(cache[@"TestRelatedEntityA"][@"value2"], @"object a2 should not be in cache"); 65 | 66 | // When 67 | // Register the new A instance 68 | cache[@"TestRelatedEntityA"][@"value2"] = a2; 69 | XCTAssertEqualObjects(cache[@"TestRelatedEntityA"][@"value2"], a2, @"object a2 should be in cache"); 70 | } 71 | 72 | - (void)testCacheAccessedEntities 73 | { 74 | // Given 75 | // Create an A instance 76 | NSManagedObject * a1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 77 | [a1 setValue:@"value1" forKey:@"attributeInA"]; 78 | 79 | // When 80 | // Create a cache of A instances 81 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 82 | @[[[KVCInstancesCache alloc] initWithContext:moc 83 | entityName:@"TestRelatedEntityA" 84 | primaryKey:@"attributeInA"]]]; 85 | 86 | // Then 87 | XCTAssertEqualObjects([cache accessedInstances], [NSSet set], @"accessed instances should be empty"); 88 | 89 | // When 90 | // Access the instance 91 | __unused id a_ = cache[@"TestRelatedEntityA"][@"value1"]; 92 | XCTAssertEqualObjects([cache accessedInstances], [NSSet setWithObject:a1], @"accessed instances should contain a1"); 93 | 94 | // When 95 | // Create a new A instance 96 | NSManagedObject * a2 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 97 | [a2 setValue:@"value2" forKey:@"attributeInA"]; 98 | // Register the new A instance 99 | cache[@"TestRelatedEntityA"][@"value2"] = a2; 100 | 101 | // Then 102 | XCTAssertEqualObjects([cache accessedInstances], ([NSSet setWithObjects:a1, a2, nil]), @"accessed instances should coutain a1 and a2"); 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /Tests/KVCMappingDictionaryTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // KVCEntityMappingTests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 13/05/13. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "KVCMapping.h" 10 | 11 | @interface KVCEntityMappingTests : XCTestCase 12 | @end 13 | 14 | @implementation KVCEntityMappingTests 15 | 16 | - (void)testParseMappingDictionarySimple 17 | { 18 | // Given 19 | id mappingDictionary = @{ @"id" : @"identifier", 20 | @"first_name" : @"firstName", 21 | @"last_name" : @"lastName" }; 22 | 23 | // When 24 | id entitymapping = [[KVCEntityMapping alloc] initWithMappingDictionary:mappingDictionary primaryKey:@"identifier" entityName:nil]; 25 | 26 | // Assert 27 | XCTAssertEqual([[entitymapping mappingsForKey:@"id"] count], (NSUInteger)1, @"There should be one key mapping"); 28 | XCTAssertEqualObjects([entitymapping primaryKey], @"identifier", @"Primary key not set"); 29 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"id"][0] property] , @"identifier", @"Key Mapping property not set"); 30 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"first_name"][0] property] , @"firstName", @"Key Mapping property not set"); 31 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"last_name"][0] property] , @"lastName", @"Key Mapping property not set"); 32 | XCTAssertNil([entitymapping mappingsForKey:@"unknown_key"], @"The mapping for an unmapped key must be nil"); 33 | } 34 | 35 | - (void)testParseMappingDictionaryMultipleMapping 36 | { 37 | // Given 38 | id mappingDictionary = @{ @"modified_at" : @[@"openDate",@"updateDate"] }; 39 | 40 | // When 41 | id entitymapping = [[KVCEntityMapping alloc] initWithMappingDictionary:mappingDictionary primaryKey:nil entityName:nil]; 42 | 43 | // Assert 44 | XCTAssertEqual(([[entitymapping mappingsForKey:@"modified_at"] count]), (NSUInteger)2, @"There should be two key mappings"); 45 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"modified_at"][0] property] , @"openDate", @"Key Mapping property not set"); 46 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"modified_at"][1] property] , @"updateDate", @"Key Mapping property not set"); 47 | } 48 | 49 | - (void)testParseMappingDictionaryWithTransformers 50 | { 51 | // Given 52 | NSValueTransformer * fakeTransformer = NSValueTransformer.new; 53 | [NSValueTransformer setValueTransformer:fakeTransformer forName:@"fake"]; 54 | id mappingDictionary = @{ @"somekey" : @"fake:someproperty" }; 55 | 56 | // When 57 | id entitymapping = [[KVCEntityMapping alloc] initWithMappingDictionary:mappingDictionary primaryKey:nil entityName:nil]; 58 | 59 | // Assert 60 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"somekey"][0] property] , @"someproperty", @"Key Mapping property not set"); 61 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"somekey"][0] transformer] , fakeTransformer, @"Key Mapping transformer not set"); 62 | } 63 | 64 | - (void)testParseMappingDictionaryWithRelationship 65 | { 66 | // Given 67 | id mappingDictionary = @{ @"partner": @"partner.identifier" }; 68 | 69 | // When 70 | id entitymapping = [[KVCEntityMapping alloc] initWithMappingDictionary:mappingDictionary primaryKey:nil entityName:nil]; 71 | 72 | // Assert 73 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"partner"][0] relationship] , @"partner", @"Relationship Mapping relationship not set"); 74 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"partner"][0] foreignKey] , @"identifier", @"Relationship Mapping foreign key not set"); 75 | } 76 | 77 | - (void)testParseMappingDictionaryWithSubobject 78 | { 79 | // Given 80 | id mappingDictionary = @{ @"partner": @{ @"partner.identifier": @{@"id": @"identifier", 81 | @"partner_name" : @"name" } 82 | } 83 | }; 84 | 85 | // When 86 | id entitymapping = [[KVCEntityMapping alloc] initWithMappingDictionary:mappingDictionary primaryKey:nil entityName:nil]; 87 | 88 | // Assert 89 | XCTAssertEqualObjects([[entitymapping mappingsForKey:@"partner"][0] relationship] , @"partner", @"Subobject Mapping relationship not set"); 90 | id submapping = [[entitymapping mappingsForKey:@"partner"][0] mapping]; 91 | XCTAssertEqualObjects([submapping primaryKey] , @"identifier", @"Subobject submapping primary key not set"); 92 | XCTAssertEqualObjects([[submapping mappingsForKey:@"partner_name"][0] property] , @"name", @"Subobject submapping primary key not set"); 93 | } 94 | 95 | 96 | - (void)testParseSimpleModelDictionary 97 | { 98 | // Given 99 | id mappingDictionary = @{ @"user" : @{@"User" : @{ @"first_name" : @"firstName", 100 | @"last_name" : @"lastName" } } }; 101 | 102 | // When 103 | id modelmapping = [[KVCModelMapping alloc] initWithMappingDictionary:mappingDictionary]; 104 | 105 | // Assert 106 | id entityMapping = [modelmapping entityMappingForKey:@"user"]; 107 | XCTAssertNotNil(entityMapping, @"Entity Mapping should be created"); 108 | XCTAssertEqualObjects([entityMapping entityName], @"User", @"Entity Mapping entity name not set"); 109 | XCTAssertNil([entityMapping primaryKey], @"Entity Mapping primary Key should be nil"); 110 | XCTAssertEqual([[entityMapping mappingsForKey:@"first_name"] count], (NSUInteger)1, @"Key Mapping should be created"); 111 | } 112 | 113 | - (void)testParseComplexModelDictionary 114 | { 115 | // Given 116 | id mappingDictionary = @{ @[@"user",@"users"] : @{@"User.identifier" : @{ @"id": @"identifier", 117 | @"first_name" : @"firstName", 118 | @"last_name" : @"lastName" } } }; 119 | 120 | // When 121 | id modelmapping = [[KVCModelMapping alloc] initWithMappingDictionary:mappingDictionary]; 122 | 123 | // Assert 124 | XCTAssertEqualObjects([modelmapping entityMappingForKey:@"user"], [modelmapping entityMappingForKey:@"users"], @"The same Entity Mapping should be set for both keys."); 125 | XCTAssertNotNil([[modelmapping entityMappingForKey:@"user"] primaryKey], @"Entity Mapping primary Key should not be nil"); 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /Tests/KVCMappingTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.appledevelopercommunity.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/NSAttributeDescription+CoercionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCMapping_Tests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "NSAttributeDescription+Coercion.h" 10 | 11 | #pragma mark Coercion Tests 12 | 13 | @interface NSManagedObject_KVCMappingCoercion_Tests : XCTestCase 14 | @end 15 | 16 | @implementation NSManagedObject_KVCMappingCoercion_Tests 17 | 18 | - (id) coerceValue:(id)value toAttributeType:(NSAttributeType)attributeType 19 | { 20 | NSAttributeDescription * attributeDesc = NSAttributeDescription.new; 21 | attributeDesc.attributeType = attributeType; 22 | return [attributeDesc kvc_coerceValue:value]; 23 | } 24 | 25 | - (void)testCoercionToNumber 26 | { 27 | // From proper strings 28 | XCTAssertEqualObjects([self coerceValue:@"1234" toAttributeType:NSInteger16AttributeType],@1234); 29 | XCTAssertEqualObjects([self coerceValue:@"1234" toAttributeType:NSInteger32AttributeType],@1234); 30 | XCTAssertEqualObjects([self coerceValue:@"1234" toAttributeType:NSInteger16AttributeType],@1234); 31 | XCTAssertEqualObjects([self coerceValue:@"1234" toAttributeType:NSInteger64AttributeType],@1234); 32 | 33 | XCTAssertEqualObjects([self coerceValue:@"12.3456789" toAttributeType:NSDecimalAttributeType],[NSDecimalNumber decimalNumberWithString:@"12.3456789"]); 34 | XCTAssertEqualObjects([self coerceValue:@"12.34" toAttributeType:NSDoubleAttributeType],@12.34); 35 | XCTAssertEqualObjects([self coerceValue:@"12.34" toAttributeType:NSFloatAttributeType],@(12.34f)); 36 | 37 | // From garbage strings 38 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSInteger16AttributeType],@0); 39 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSInteger32AttributeType],@0); 40 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSInteger16AttributeType],@0); 41 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSInteger64AttributeType],@0); 42 | 43 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSDecimalAttributeType],[NSDecimalNumber notANumber]); 44 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSDoubleAttributeType],@0); 45 | XCTAssertEqualObjects([self coerceValue:@"toto" toAttributeType:NSFloatAttributeType],@0); 46 | 47 | // From numbers 48 | XCTAssertEqualObjects([self coerceValue:@1234 toAttributeType:NSInteger16AttributeType],@1234); 49 | XCTAssertEqualObjects([self coerceValue:@1234 toAttributeType:NSInteger32AttributeType],@1234); 50 | XCTAssertEqualObjects([self coerceValue:@1234 toAttributeType:NSInteger16AttributeType],@1234); 51 | XCTAssertEqualObjects([self coerceValue:@1234 toAttributeType:NSInteger64AttributeType],@1234); 52 | 53 | XCTAssertEqualObjects([self coerceValue:@12.3456789 toAttributeType:NSDecimalAttributeType],[NSDecimalNumber decimalNumberWithString:@"12.3456789"]); 54 | XCTAssertEqualObjects([self coerceValue:@12.34 toAttributeType:NSDoubleAttributeType],@12.34); 55 | XCTAssertEqualObjects([self coerceValue:@(12.34f) toAttributeType:NSFloatAttributeType],@(12.34f)); 56 | 57 | // From nil 58 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSInteger16AttributeType],nil); 59 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSInteger32AttributeType],nil); 60 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSInteger16AttributeType],nil); 61 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSInteger64AttributeType],nil); 62 | 63 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSDecimalAttributeType],nil); 64 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSDoubleAttributeType],nil); 65 | XCTAssertEqualObjects([self coerceValue:nil toAttributeType:NSFloatAttributeType],nil); 66 | 67 | // From garbage data 68 | id value = [NSData dataWithBytes:"bla" length:4]; 69 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSInteger16AttributeType],nil); 70 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSInteger32AttributeType],nil); 71 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSInteger16AttributeType],nil); 72 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSInteger64AttributeType],nil); 73 | 74 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSDecimalAttributeType],nil); 75 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSDoubleAttributeType],nil); 76 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSFloatAttributeType],nil); 77 | } 78 | 79 | - (void)testCoercionToString 80 | { 81 | XCTAssertEqualObjects([self coerceValue:@"abcd" toAttributeType:NSStringAttributeType],@"abcd"); 82 | XCTAssertEqualObjects([self coerceValue:@1234 toAttributeType:NSStringAttributeType],@"1234"); 83 | XCTAssertEqualObjects([self coerceValue:@12.34 toAttributeType:NSStringAttributeType],@"12.34"); 84 | } 85 | 86 | - (void)testCoercionToBool 87 | { 88 | XCTAssertEqualObjects([self coerceValue:@YES toAttributeType:NSBooleanAttributeType],@YES); 89 | XCTAssertEqualObjects([self coerceValue:@"true" toAttributeType:NSBooleanAttributeType],@YES); 90 | XCTAssertEqualObjects([self coerceValue:@"false" toAttributeType:NSBooleanAttributeType],@NO); 91 | XCTAssertEqualObjects([self coerceValue:@"abcd" toAttributeType:NSBooleanAttributeType],@NO); 92 | } 93 | 94 | - (void)testCoercionToDate 95 | { 96 | NSDate * date = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; 97 | XCTAssertEqualObjects([self coerceValue:date toAttributeType:NSDateAttributeType],date); 98 | XCTAssertEqualObjects([self coerceValue:@"abcd" toAttributeType:NSDateAttributeType],nil); 99 | } 100 | 101 | - (void)testCoercionToData 102 | { 103 | NSData * data = [NSData dataWithBytes:"abcd" length:5]; 104 | XCTAssertEqualObjects([self coerceValue:data toAttributeType:NSBinaryDataAttributeType],data); 105 | XCTAssertEqualObjects([self coerceValue:@"abcd" toAttributeType:NSBinaryDataAttributeType],nil); 106 | } 107 | 108 | - (void)testCoercionToOtherTypes 109 | { 110 | // Passed-through 111 | id value = NSObject.new; 112 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSUndefinedAttributeType],value); 113 | XCTAssertEqualObjects([self coerceValue:value toAttributeType:NSTransformableAttributeType],value); 114 | } 115 | 116 | - (void)testFixNumberType 117 | { 118 | NSAttributeDescription * attributeDesc = NSAttributeDescription.new; 119 | attributeDesc.attributeType = NSBooleanAttributeType; 120 | XCTAssertEqual(CFNumberGetType((__bridge CFNumberRef)[attributeDesc kvc_fixNumberValueType:@1]), kCFNumberCharType); 121 | 122 | attributeDesc.attributeType = NSInteger16AttributeType; 123 | XCTAssertEqual(CFNumberGetType((__bridge CFNumberRef)[attributeDesc kvc_fixNumberValueType:@YES]), kCFNumberSInt16Type); 124 | 125 | attributeDesc.attributeType = NSDoubleAttributeType; 126 | XCTAssertEqual(CFNumberGetType((__bridge CFNumberRef)[attributeDesc kvc_fixNumberValueType:@1]), kCFNumberFloat64Type); 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /Tests/NSEntityDescription+KVCFetchingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCFetching_Tests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "KVCMapping.h" 10 | 11 | /****************************************************************************/ 12 | #pragma mark Entity Class 13 | 14 | @interface TestEntityClass : NSManagedObject 15 | @property NSString * testAttribute; 16 | @end 17 | 18 | @implementation TestEntityClass 19 | @dynamic testAttribute; 20 | @end 21 | 22 | /****************************************************************************/ 23 | #pragma mark Fetch Tests 24 | 25 | @interface NSManagedObject_KVCFetching_Tests : XCTestCase 26 | @end 27 | 28 | @implementation NSManagedObject_KVCFetching_Tests 29 | { 30 | @protected 31 | NSManagedObjectContext * moc; 32 | } 33 | 34 | - (void)setUp 35 | { 36 | [super setUp]; 37 | 38 | moc = NSManagedObjectContext.new; 39 | moc.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: 40 | [[NSManagedObjectModel alloc] initWithContentsOfURL: 41 | [[NSBundle bundleForClass:self.class] URLForResource:@"NSManagedObject_KVCMapping_Tests" 42 | withExtension:@"mom"]]]; 43 | } 44 | 45 | - (void)testFetchObject 46 | { 47 | TestEntityClass * objectA = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntityWithClass" inManagedObjectContext:moc]; 48 | objectA.testAttribute = @"A"; 49 | TestEntityClass * objectB = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntityWithClass" inManagedObjectContext:moc]; 50 | objectB.testAttribute = @"B"; 51 | 52 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:nil], objectA); 53 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"B" forKey:@"testAttribute" options:nil], objectB); 54 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"C" forKey:@"testAttribute" options:nil], nil); 55 | } 56 | 57 | - (void)testFetchObjectWithCoercion 58 | { 59 | TestEntityClass * objectA = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntityWithClass" inManagedObjectContext:moc]; 60 | objectA.testAttribute = @"1234"; 61 | 62 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@1234 forKey:@"testAttribute" options:nil], objectA); 63 | } 64 | 65 | - (void)testCreateOrFetchObject 66 | { 67 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:nil], nil); 68 | TestEntityClass * objectA = [TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:@{KVCCreateObjectOption:@YES}]; 69 | XCTAssertNotNil(objectA); 70 | objectA.testAttribute = @"A"; 71 | 72 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:nil], objectA); 73 | } 74 | 75 | - (void)testFetchObjectWithCache 76 | { 77 | TestEntityClass * objectA = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntityWithClass" inManagedObjectContext:moc]; 78 | objectA.testAttribute = @"A"; 79 | 80 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 81 | @[[[KVCInstancesCache alloc] initWithContext:moc 82 | entityName:@"TestEntityWithClass" 83 | primaryKey:@"testAttribute"]]]; 84 | 85 | XCTAssertEqualObjects(cache[@"TestEntityWithClass"][@"A"], objectA); 86 | 87 | XCTAssertEqualObjects([TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:(@{KVCCreateObjectOption:@YES, KVCEntitiesCacheOption:cache})], objectA); 88 | } 89 | 90 | - (void)testCreateObjectWithCacheMiss 91 | { 92 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 93 | @[[[KVCInstancesCache alloc] initWithContext:moc 94 | entityName:@"TestEntityWithClass" 95 | primaryKey:@"testAttribute"]]]; 96 | 97 | TestEntityClass * objectA = [TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:(@{KVCEntitiesCacheOption:cache})]; 98 | XCTAssertNil(objectA); 99 | 100 | objectA = [TestEntityClass kvc_fetchObjectInContext:moc withValue:@"A" forKey:@"testAttribute" options:(@{KVCCreateObjectOption:@YES, KVCEntitiesCacheOption:cache})]; 101 | XCTAssertNotNil(objectA); 102 | XCTAssertEqualObjects(objectA.testAttribute, @"A"); 103 | 104 | XCTAssertEqualObjects(cache[@"TestEntityWithClass"][@"A"], objectA); 105 | } 106 | 107 | @end 108 | 109 | 110 | -------------------------------------------------------------------------------- /Tests/NSManagedObject+KVCMappingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCMapping_Tests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "NSObject+KVCMapping.h" 10 | 11 | @interface NSManagedObject_KVCMapping_Tests : XCTestCase 12 | @end 13 | 14 | @implementation NSManagedObject_KVCMapping_Tests 15 | { 16 | NSManagedObjectContext * _moc; 17 | NSDictionary * _mapping; 18 | NSDictionary * _goodDataset, * _badDataSet; 19 | } 20 | 21 | - (void)setUp 22 | { 23 | [super setUp]; 24 | 25 | _moc = NSManagedObjectContext.new; 26 | _moc.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: 27 | [[NSManagedObjectModel alloc] initWithContentsOfURL: 28 | [[NSBundle bundleForClass:self.class] URLForResource:@"NSManagedObject_KVCMapping_Tests" 29 | withExtension:@"mom"]]]; 30 | 31 | NSDate * date = NSDate.date; 32 | 33 | _mapping = @{@"usedBoolean": @"actualBoolean", 34 | @"usedData": @"actualData", 35 | @"usedDate": @"actualDate", 36 | @"usedDecimal": @"actualDecimal", 37 | @"usedDouble": @"actualDouble", 38 | @"usedFloat": @"actualFloat", 39 | @"usedInt16": @"actualInt16", 40 | @"usedInt32": @"actualInt32", 41 | @"usedInt64": @"actualInt64", 42 | @"usedString": @"actualString", 43 | }; 44 | 45 | _goodDataset = @{@"usedBoolean": @YES, 46 | @"usedInt16": [NSNumber numberWithShort:100], 47 | @"usedInt32": @100, 48 | @"usedInt64": @100LL, 49 | @"usedDecimal": [NSDecimalNumber numberWithInt:100], 50 | @"usedFloat": @100.0f, 51 | @"usedDouble": @100.0, 52 | @"usedString": @"100", 53 | @"usedData": [@"test" dataUsingEncoding:NSUTF8StringEncoding], 54 | @"usedDate": date 55 | }; 56 | 57 | _badDataSet = @{@"usedBoolean": @"YES", 58 | @"usedInt16": @"100", 59 | @"usedInt32": @"100", 60 | @"usedInt64": @"100", 61 | @"usedDecimal": @"100", 62 | @"usedFloat": @"100", 63 | @"usedDouble": @"100", 64 | @"usedString": @100, 65 | @"usedData": [@"test" dataUsingEncoding:NSUTF8StringEncoding], 66 | @"usedDate": date}; 67 | } 68 | 69 | - (void)testSimpleDataset 70 | { 71 | // Checks the values in the goodDataSet are correctly set 72 | NSManagedObject * test = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity" inManagedObjectContext:_moc]; 73 | 74 | [test kvc_setValues:_goodDataset withMappingDictionary:_mapping options:nil]; 75 | 76 | for (NSString * wantedKey in _goodDataset) { 77 | id value = _goodDataset[wantedKey]; 78 | NSString * realKey = _mapping[wantedKey]; 79 | XCTAssertEqualObjects([test valueForKey:realKey], value); 80 | } 81 | // reverse 82 | id values = [test kvc_valuesWithMappingDictionary:_mapping options:nil]; 83 | 84 | XCTAssertEqualObjects(values, _goodDataset); 85 | } 86 | 87 | - (void)testAutomaticCoercionDataset 88 | { 89 | // Checks the values from the badDataSet are converted to values equal to the ones in the goodDataSet 90 | NSManagedObject * test = [NSEntityDescription insertNewObjectForEntityForName:@"TestEntity" inManagedObjectContext:_moc]; 91 | 92 | [test kvc_setValues:_badDataSet withMappingDictionary:_mapping options:nil]; 93 | 94 | for (NSString * wantedKey in _goodDataset) { 95 | id value = _goodDataset[wantedKey]; 96 | NSString * realKey = _mapping[wantedKey]; 97 | XCTAssertEqualObjects([test valueForKey:realKey], value); 98 | } 99 | 100 | // reverse 101 | id values = [test kvc_valuesWithMappingDictionary:_mapping options:nil]; 102 | 103 | XCTAssertEqualObjects(values, _goodDataset); 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /Tests/NSManagedObject+KVCRelationshipTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCRelationshipTests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "KVCMapping.h" 10 | 11 | /****************************************************************************/ 12 | #pragma mark Fetch Tests 13 | 14 | @interface NSManagedObject_KVCRelationship_Tests : XCTestCase 15 | @end 16 | 17 | @implementation NSManagedObject_KVCRelationship_Tests 18 | { 19 | @protected 20 | NSManagedObjectContext * moc; 21 | } 22 | 23 | - (void)setUp 24 | { 25 | [super setUp]; 26 | 27 | moc = NSManagedObjectContext.new; 28 | moc.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: 29 | [[NSManagedObjectModel alloc] initWithContentsOfURL: 30 | [[NSBundle bundleForClass:self.class] URLForResource:@"NSManagedObject_KVCMapping_Tests" 31 | withExtension:@"mom"]]]; 32 | } 33 | 34 | - (void)testSetRelationship 35 | { 36 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 37 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 38 | 39 | [a setValue:@"value1" forKey:@"attributeInA"]; 40 | 41 | [b kvc_setRelationship:@"relationToA" toObjectWithValue:@"value1" forKey:@"attributeInA" options:nil]; 42 | 43 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], a); 44 | XCTAssertEqualObjects([a valueForKey:@"relationToB"], b); 45 | } 46 | 47 | - (void)testSetRelationshipToMany 48 | { 49 | NSManagedObject * a1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 50 | NSManagedObject * a2 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 51 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 52 | 53 | [a1 setValue:@"value1" forKey:@"attributeInA"]; 54 | [a2 setValue:@"value2" forKey:@"attributeInA"]; 55 | 56 | [b kvc_setRelationship:@"relationToManyAs" toObjectsWithValueIn:@[@"value1",@"value2"] forKey:@"attributeInA" options:nil]; 57 | 58 | XCTAssertEqualObjects([b valueForKey:@"relationToManyAs"], ([NSSet setWithObjects:a1, a2, nil])); 59 | 60 | // It works too if a single value is passed 61 | [b kvc_setRelationship:@"relationToManyAs" toObjectsWithValueIn:@[@"value1"] forKey:@"attributeInA" options:nil]; 62 | 63 | XCTAssertEqualObjects([b valueForKey:@"relationToManyAs"], [NSSet setWithObject:a1]); 64 | 65 | // It replaces (doesn't add) when we set to another 66 | [b kvc_setRelationship:@"relationToManyAs" toObjectsWithValueIn:@[@"value2"] forKey:@"attributeInA" options:nil]; 67 | 68 | XCTAssertEqualObjects([b valueForKey:@"relationToManyAs"], [NSSet setWithObject:a2]); 69 | } 70 | 71 | - (void)testSetRelationshipWithCoercion 72 | { 73 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 74 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 75 | 76 | [a setValue:@"1234" forKey:@"attributeInA"]; 77 | 78 | [b kvc_setRelationship:@"relationToA" toObjectWithValue:@1234 forKey:@"attributeInA" options:nil]; 79 | 80 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], a); 81 | XCTAssertEqualObjects([a valueForKey:@"relationToB"], b); 82 | } 83 | 84 | - (void)testSetRelationshipToNonexistingObject 85 | { 86 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 87 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 88 | 89 | [a setValue:@"value1" forKey:@"attributeInA"]; 90 | 91 | [b kvc_setRelationship:@"relationToA" toObjectWithValue:@"value2" forKey:@"attributeInA" options:nil]; 92 | 93 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], nil); 94 | } 95 | 96 | - (void)testSetRelationshipToNonexistingObjectAndCreate 97 | { 98 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 99 | 100 | [a kvc_setRelationship:@"relationToB" toObjectWithValue:@"value1" forKey:@"attributeInB" options:(@{KVCCreateObjectOption:@YES})]; 101 | NSManagedObject * b = [a valueForKey:@"relationToB"]; 102 | 103 | XCTAssertNotNil(b); 104 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], a); 105 | } 106 | 107 | - (void)testSetRelationshipWithCache 108 | { 109 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 110 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 111 | [a setValue:@"value1" forKey:@"attributeInA"]; 112 | 113 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 114 | @[[[KVCInstancesCache alloc] initWithContext:moc 115 | entityName:@"TestRelatedEntityA" 116 | primaryKey:@"attributeInA"]]]; 117 | 118 | [b kvc_setRelationship:@"relationToA" toObjectWithValue:@"value1" forKey:@"attributeInA" options:@{KVCEntitiesCacheOption: cache}]; 119 | 120 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], a); 121 | } 122 | 123 | - (void)testSetRelationshipWithCacheMissAndCreateObject 124 | { 125 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 126 | 127 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 128 | @[[[KVCInstancesCache alloc] initWithContext:moc 129 | entityName:@"TestRelatedEntityB" 130 | primaryKey:@"attributeInA"]]]; 131 | 132 | [a kvc_setRelationship:@"relationToB" toObjectWithValue:@"value1" forKey:@"attributeInB" options:@{KVCCreateObjectOption: @YES, KVCEntitiesCacheOption: cache}]; 133 | NSManagedObject * b = [a valueForKey:@"relationToB"]; 134 | 135 | XCTAssertNotNil(b); 136 | XCTAssertEqualObjects(cache[@"TestRelatedEntityB"][@"value1"], b); 137 | } 138 | 139 | - (void)testSetRelationshipToMissingEntity 140 | { 141 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 142 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 143 | [a setValue:@"value1" forKey:@"attributeInA"]; 144 | [b setValue:@"value1" forKey:@"attributeInB"]; 145 | KVCEntitiesCache * cache = [[KVCEntitiesCache alloc] initWithInstanceCaches: 146 | @[[[KVCInstancesCache alloc] initWithContext:moc 147 | entityName:@"TestRelatedEntityA" 148 | primaryKey:@"attributeInA"]]]; 149 | // Entity B is not in the cache, but will be fetched regularly 150 | [a kvc_setRelationship:@"relationToB" toObjectWithValue:@"value1" forKey:@"attributeInB" options:@{KVCCreateObjectOption: @YES, KVCEntitiesCacheOption: cache}]; 151 | NSManagedObject * b2 = [a valueForKey:@"relationToB"]; 152 | XCTAssertEqualObjects(b, b2); 153 | } 154 | 155 | - (void)testSetRelationshipWithDictionary 156 | { 157 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 158 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 159 | 160 | [a setValue:@"value1" forKey:@"attributeInA"]; 161 | 162 | [b kvc_setValues:@{@"a" : @"value1"} withMappingDictionary:@{@"a": @"relationToA.attributeInA"} options:nil]; 163 | 164 | XCTAssertEqualObjects([b valueForKey:@"relationToA"], a); 165 | XCTAssertEqualObjects([a valueForKey:@"relationToB"], b); 166 | } 167 | 168 | - (void)testGetReverseRelationshipWithDictionary 169 | { 170 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 171 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 172 | 173 | [a setValue:@"value1" forKey:@"attributeInA"]; 174 | [b setValue:a forKey:@"relationToA"]; 175 | 176 | id values = [b kvc_valuesWithMappingDictionary:@{@"a": @"relationToA.attributeInA"} options:nil]; 177 | 178 | XCTAssertEqualObjects(values, @{@"a": @"value1"}); 179 | } 180 | 181 | - (void)testGetReverseRelationshipWithDictionaryToMany 182 | { 183 | NSManagedObject * a1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 184 | NSManagedObject * a2 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 185 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 186 | 187 | [a1 setValue:@"value1" forKey:@"attributeInA"]; 188 | [a2 setValue:@"value2" forKey:@"attributeInA"]; 189 | [b setValue:[NSSet setWithArray:@[a1, a2 ]] forKey:@"relationToManyAs"]; 190 | 191 | // To many relationships are ignored by default 192 | id values = [b kvc_valuesWithMappingDictionary:@{@"a": @"relationToManyAs.attributeInA"} options:nil]; 193 | XCTAssertEqualObjects(values, @{}); 194 | 195 | values = [b kvc_valuesWithMappingDictionary:@{@"a": @"relationToManyAs.attributeInA"} options:@{KVCIncludeToManyRelationshipsOption: @YES}]; 196 | XCTAssertEqualObjects([NSSet setWithArray:[values valueForKey:@"a"]], [NSSet setWithArray:(@[@"value1", @"value2"])]); 197 | } 198 | 199 | - (void)testSetRelationshipWithSubobjectMapping 200 | { 201 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 202 | 203 | id mappingDictionary = @{ @"b" : @{ @"relationToB" : @{@"attr": @"attributeInB" }}}; 204 | 205 | id values = @{@"b": @{@"attr":@"VALUE"}}; 206 | 207 | [a kvc_setValues:values withMappingDictionary:mappingDictionary options:nil]; 208 | 209 | NSManagedObject * b = [a valueForKey:@"relationToB"]; 210 | XCTAssertNotNil(b); 211 | XCTAssertEqualObjects([b valueForKey:@"attributeInB"], @"VALUE"); 212 | } 213 | 214 | - (void)testSetRelationshipWithManySubobjectMapping 215 | { 216 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 217 | 218 | id mappingDictionary = @{ @"b" : @{ @"relationToManyBs" : @{@"attr": @"attributeInB" }}}; 219 | 220 | id values = @{@"b": @[@{@"attr":@"VALUE_1"},@{@"attr":@"VALUE_2"}]}; 221 | 222 | [a kvc_setValues:values withMappingDictionary:mappingDictionary options:nil]; 223 | 224 | NSSet * b = [a valueForKey:@"relationToManyBs"]; 225 | 226 | XCTAssertEqual([b count], (NSUInteger)2); 227 | XCTAssertEqualObjects([b valueForKey:@"attributeInB"], ([NSSet setWithArray:@[@"VALUE_1", @"VALUE_2" ]])); 228 | } 229 | 230 | - (void)testSetRelationshipWithExistingSubobjectMapping 231 | { 232 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 233 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 234 | [b setValue:@"VALUE" forKey:@"attributeInB"]; 235 | 236 | id mappingDictionary = @{ @"b" : @{ @"relationToB.attributeInB" : @{@"attr": @"attributeInB" }}}; 237 | 238 | id values = @{@"b": @{@"attr":@"VALUE"}}; 239 | 240 | [a kvc_setValues:values withMappingDictionary:mappingDictionary options:nil]; 241 | 242 | NSManagedObject * newb = [a valueForKey:@"relationToB"]; 243 | 244 | XCTAssertEqual(b, newb); 245 | } 246 | 247 | - (void)testGetReverseRelationshipWithSubobjectMapping 248 | { 249 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 250 | NSManagedObject * b = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 251 | [b setValue:@"VALUE" forKey:@"attributeInB"]; 252 | [a setValue:b forKey:@"relationToB"]; 253 | 254 | id mappingDictionary = @{ @"b" : @{ @"relationToB" : @{@"attr": @"attributeInB" }}}; 255 | 256 | // Subobjects are ignored by default 257 | id values = [a kvc_valuesWithMappingDictionary:mappingDictionary options:nil]; 258 | XCTAssertEqualObjects(values, @{}); 259 | 260 | values = [a kvc_valuesWithMappingDictionary:mappingDictionary options:@{KVCIncludeSubobjectsOption: @YES}]; 261 | XCTAssertEqualObjects(values, @{@"b": @{@"attr":@"VALUE"}}); 262 | } 263 | 264 | - (void)testGetReverseRelationshipWithManySubobjectsMapping 265 | { 266 | NSManagedObject * a = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityA" inManagedObjectContext:moc]; 267 | NSManagedObject * b1 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 268 | NSManagedObject * b2 = [NSEntityDescription insertNewObjectForEntityForName:@"TestRelatedEntityB" inManagedObjectContext:moc]; 269 | [b1 setValue:@"VALUE_1" forKey:@"attributeInB"]; 270 | [b2 setValue:@"VALUE_2" forKey:@"attributeInB"]; 271 | [a setValue:[NSSet setWithArray:@[b1, b2]] forKey:@"relationToManyBs"]; 272 | 273 | id mappingDictionary = @{ @"b" : @{ @"relationToManyBs" : @{@"attr": @"attributeInB" }}}; 274 | 275 | // Subobjects are ignored by default 276 | id values = [a kvc_valuesWithMappingDictionary:mappingDictionary options:nil]; 277 | XCTAssertEqualObjects(values, @{}); 278 | 279 | values = [a kvc_valuesWithMappingDictionary:mappingDictionary options:@{KVCIncludeSubobjectsOption: @YES}]; 280 | XCTAssertEqualObjects([NSSet setWithArray:[values valueForKey:@"b"]], [NSSet setWithArray:(@[@{@"attr":@"VALUE_1"}, @{@"attr":@"VALUE_2"}])]); 281 | } 282 | 283 | @end 284 | -------------------------------------------------------------------------------- /Tests/NSManagedObject_KVCMapping_Tests.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tests/NSObject+KVCMappingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KVCMapping_Tests.m 3 | // KVCMapping 4 | // 5 | // Created by Nicolas Bouilleaud on 27/11/11. 6 | // Copyright (c) 2011 Nicolas Bouilleaud. All rights reserved. 7 | // 8 | #import 9 | #import "KVCMapping.h" 10 | 11 | #pragma mark Forward Declarations 12 | 13 | @interface UppercaseTransformer : NSValueTransformer 14 | @end 15 | @interface LowercaseTransformer : NSValueTransformer 16 | @end 17 | @interface OppositeNumberTransformer : NSValueTransformer 18 | @end 19 | 20 | #pragma mark Test class 21 | 22 | @interface TestClass : NSObject 23 | @property id actualProperty1; 24 | @property id actualProperty2; 25 | @end 26 | 27 | @implementation TestClass 28 | @end 29 | 30 | /****************************************************************************/ 31 | #pragma mark Basic Tests 32 | 33 | @interface NSObject_KVCMapping_Tests : XCTestCase 34 | @end 35 | 36 | @implementation NSObject_KVCMapping_Tests 37 | 38 | - (void)testBasic 39 | { 40 | TestClass * test = TestClass.new; 41 | 42 | [test kvc_setValue:@"testValue" forKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"actualProperty1"} options:nil ]; 43 | 44 | XCTAssertThrows([test valueForKey:@"usedName"]); 45 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"testValue"); 46 | } 47 | 48 | - (void)testMultipleKey 49 | { 50 | TestClass * test = TestClass.new; 51 | 52 | [test kvc_setValues:@{@"usedProperty1" : @"testValue1", @"usedProperty2" : @"testValue2"} 53 | withMappingDictionary:@{@"usedProperty1": @"actualProperty1", @"usedProperty2": @"actualProperty2"} 54 | options:nil]; 55 | 56 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"testValue1"); 57 | XCTAssertEqualObjects([test valueForKey:@"actualProperty2"], @"testValue2"); 58 | } 59 | 60 | - (void)testValuesFromArray 61 | { 62 | TestClass * test = TestClass.new; 63 | 64 | [test kvc_setValues:@[@"testValue1", @"testValue2"] 65 | withMappingDictionary:@{@0: @"actualProperty1", @1: @"actualProperty2"} 66 | options:nil]; 67 | 68 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"testValue1"); 69 | XCTAssertEqualObjects([test valueForKey:@"actualProperty2"], @"testValue2"); 70 | } 71 | 72 | - (void)testCompositeValue 73 | { 74 | TestClass * test = TestClass.new; 75 | 76 | [test kvc_setValue:@"testValue" forKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @[@"actualProperty1",@"actualProperty2"]} options:nil]; 77 | 78 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"testValue"); 79 | XCTAssertEqualObjects([test valueForKey:@"actualProperty2"], @"testValue"); 80 | } 81 | 82 | - (void)testKVCSimpleValueTransformer 83 | { 84 | NSValueTransformer setValueTransformer:[UppercaseTransformer.new forName:@"uppercase"]; 85 | TestClass * test = TestClass.new; 86 | 87 | [test kvc_setValue:@"testValue" forKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"uppercase:actualProperty1"} options:nil]; 88 | 89 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"TESTVALUE"); 90 | } 91 | 92 | - (void)testCompositeValueTransformer 93 | { 94 | NSValueTransformer setValueTransformer:[UppercaseTransformer.new forName:@"uppercase"]; 95 | NSValueTransformer setValueTransformer:[LowercaseTransformer.new forName:@"lowercase"]; 96 | TestClass * test = TestClass.new; 97 | 98 | [test kvc_setValue:@"testValue" forKey:@"usedProperty" withMappingDictionary: 99 | @{@"usedProperty": @[@"uppercase:actualProperty1", 100 | @"lowercase:actualProperty2"]} options:nil]; 101 | 102 | XCTAssertEqualObjects([test valueForKey:@"actualProperty1"], @"TESTVALUE"); 103 | XCTAssertEqualObjects([test valueForKey:@"actualProperty2"], @"testvalue"); 104 | } 105 | 106 | @end 107 | 108 | #pragma mark - 109 | 110 | @implementation UppercaseTransformer 111 | + (BOOL)allowsReverseTransformation { return NO; } 112 | + (Class) transformedValueClass; { return [NSString class]; } 113 | - (id) transformedValue:(id)value { return [value uppercaseString]; } 114 | @end 115 | 116 | @implementation LowercaseTransformer 117 | + (BOOL)allowsReverseTransformation { return NO; } 118 | + (Class) transformedValueClass; { return [NSString class]; } 119 | - (id) transformedValue:(id)value { return [value lowercaseString]; } 120 | @end 121 | 122 | @implementation OppositeNumberTransformer 123 | + (BOOL)allowsReverseTransformation { return YES; } 124 | + (Class) transformedValueClass; { return [NSNumber class]; } 125 | - (id) transformedValue:(id)value { return value ? @(-[value doubleValue]) : nil; } 126 | @end 127 | 128 | /****************************************************************************/ 129 | #pragma mark Reverse Mapping Tests 130 | 131 | @interface NSObject_KVCReverseMapping_Tests : XCTestCase 132 | @end 133 | 134 | @implementation NSObject_KVCReverseMapping_Tests 135 | 136 | - (void)testBasic 137 | { 138 | TestClass * test = TestClass.new; 139 | test.actualProperty1 = @"testValue1"; 140 | 141 | id value = [test kvc_valueForKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"actualProperty1"} options:nil]; 142 | 143 | XCTAssertEqualObjects(value, @"testValue1"); 144 | } 145 | 146 | - (void)testMultipleKey 147 | { 148 | TestClass * test = TestClass.new; 149 | test.actualProperty1 = @"testValue1"; 150 | test.actualProperty2 = @"testValue2"; 151 | 152 | id values = [test kvc_valuesWithMappingDictionary:@{@"usedProperty1": @"actualProperty1", @"usedProperty2": @"actualProperty2"} 153 | options:nil]; 154 | 155 | XCTAssertEqualObjects(values[@"usedProperty1"], @"testValue1"); 156 | XCTAssertEqualObjects(values[@"usedProperty2"], @"testValue2"); 157 | } 158 | 159 | - (void)testNilValues 160 | { 161 | TestClass * test = TestClass.new; 162 | test.actualProperty1 = nil; 163 | 164 | id values = [test kvc_valuesWithMappingDictionary:@{@"usedProperty1": @"actualProperty1"} 165 | options:nil]; 166 | 167 | XCTAssertEqualObjects(values[@"usedProperty1"], [NSNull null]); 168 | } 169 | 170 | - (void)testValuesFromArray 171 | { 172 | TestClass * test = TestClass.new; 173 | test.actualProperty1 = @"testValue1"; 174 | test.actualProperty2 = @"testValue2"; 175 | 176 | id values = [test kvc_valuesWithMappingDictionary:@{@0: @"actualProperty1", @1: @"actualProperty2"} 177 | options:nil]; 178 | 179 | XCTAssertEqualObjects(values[0], @"testValue1"); 180 | XCTAssertEqualObjects(values[1], @"testValue2"); 181 | } 182 | 183 | - (void)testCompositeValue 184 | { 185 | TestClass * test = TestClass.new; 186 | test.actualProperty1 = @"testValue1"; 187 | test.actualProperty2 = @"testValue2"; 188 | 189 | id value = [test kvc_valueForKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @[@"actualProperty1",@"actualProperty2"]} options:nil]; 190 | 191 | XCTAssertEqualObjects(value, @"testValue1"); 192 | } 193 | 194 | - (void)testUnsupportedValueTransformer 195 | { 196 | NSValueTransformer setValueTransformer:[UppercaseTransformer.new forName:@"uppercase"]; 197 | TestClass * test = TestClass.new; 198 | test.actualProperty1 = @"testValue1"; 199 | 200 | id value = [test kvc_valueForKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"uppercase:actualProperty1"} options:nil]; 201 | 202 | XCTAssertNil(value); 203 | } 204 | 205 | - (void)testSupportedValueTransformer 206 | { 207 | NSValueTransformer setValueTransformer:[OppositeNumberTransformer.new forName:@"opposite"]; 208 | TestClass * test = TestClass.new; 209 | test.actualProperty1 = @123; 210 | 211 | id value = [test kvc_valueForKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"opposite:actualProperty1"} options:nil]; 212 | 213 | XCTAssertEqualObjects(value, @(-123)); 214 | } 215 | 216 | - (void)testSupportedValueTransformerWithNilValue 217 | { 218 | NSValueTransformer setValueTransformer:[OppositeNumberTransformer.new forName:@"opposite"]; 219 | TestClass * test = TestClass.new; 220 | test.actualProperty1 = nil; 221 | 222 | id value = [test kvc_valueForKey:@"usedProperty" withMappingDictionary:@{@"usedProperty": @"opposite:actualProperty1"} options:nil]; 223 | 224 | XCTAssertEqualObjects(value, [NSNull null]); 225 | } 226 | 227 | @end 228 | --------------------------------------------------------------------------------