├── .gitignore ├── AlecrimCoreData.png ├── AlecrimCoreData.podspec ├── AlecrimCoreData.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── AlecrimCoreData.xcscheme ├── CHANGELOG.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Convenience │ ├── EntityObserver.swift │ ├── FetchRequestController+Extensions.swift │ ├── ManagedObjectContextType.swift │ ├── NSArrayController+Extensions.swift │ ├── NSCollectionView+Extensions.swift │ ├── NSTableView+Extensions.swift │ ├── UICollectionView+Extensions.swift │ └── UITableView+Extensions.swift ├── Core │ ├── Persistent Container │ │ ├── CustomPersistentContainer.swift │ │ ├── ManagedObject.swift │ │ ├── ManagedObjectContext.swift │ │ ├── PersistentContainer.swift │ │ ├── PersistentContainerAuxiliarTypes.swift │ │ └── PersistentContainerType.swift │ └── Query │ │ ├── Config.swift │ │ ├── Expression.swift │ │ ├── FetchRequest.swift │ │ ├── KeyPath.swift │ │ ├── Predicate.swift │ │ ├── Query.swift │ │ ├── Queryable.swift │ │ └── SortDescriptor.swift └── Fetch Request Controller │ ├── FetchRequestController.swift │ ├── FetchedResultsControllerDelegate.swift │ └── FetchedResultsSectionInfo.swift ├── Supporting Files ├── AlecrimCoreData.h ├── AlecrimCoreData.xcconfig └── Info.plist └── Tests ├── AlecrimCoreDataTests.swift └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | # 51 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | 73 | # Code Injection 74 | # 75 | # After new code Injection tools there's a generated folder /iOSInjectionProject 76 | # https://github.com/johnno1962/injectionforxcode 77 | 78 | iOSInjectionProject/ 79 | -------------------------------------------------------------------------------- /AlecrimCoreData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alecrim/AlecrimCoreData/4571d3215997d2550f967736d746245bc635dcbb/AlecrimCoreData.png -------------------------------------------------------------------------------- /AlecrimCoreData.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AlecrimCoreData' 3 | s.version = '7.0' 4 | s.summary = 'Core Data made simple' 5 | 6 | s.homepage = 'https://github.com/Alecrim/AlecrimCoreData' 7 | s.license = 'MIT' 8 | s.author = { 'Vanderlei Martinelli' => 'vanderlei.martinelli@gmail.com' } 9 | s.social_media_url = 'https://www.linkedin.com/in/vmartinelli' 10 | 11 | s.source = { :git => 'https://github.com/Alecrim/AlecrimCoreData.git', :tag => s.version.to_s } 12 | s.source_files = 'Sources/**/*' 13 | 14 | s.osx.deployment_target = '10.12' 15 | s.ios.deployment_target = '10.0' 16 | s.watchos.deployment_target = '3.0' 17 | s.tvos.deployment_target = '10.0' 18 | end 19 | -------------------------------------------------------------------------------- /AlecrimCoreData.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 145008DE2261881400C0E828 /* AlecrimCoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 145008D42261881300C0E828 /* AlecrimCoreData.framework */; }; 11 | 145008E32261881400C0E828 /* AlecrimCoreDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008E22261881400C0E828 /* AlecrimCoreDataTests.swift */; }; 12 | 145008E52261881400C0E828 /* AlecrimCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 145008D72261881300C0E828 /* AlecrimCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 145008F022618CA700C0E828 /* AlecrimCoreData.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 145008EF22618CA700C0E828 /* AlecrimCoreData.xcconfig */; }; 14 | 1450091622619F3F00C0E828 /* FetchRequestController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008F922619F3F00C0E828 /* FetchRequestController.swift */; }; 15 | 1450091722619F3F00C0E828 /* FetchedResultsSectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008FA22619F3F00C0E828 /* FetchedResultsSectionInfo.swift */; }; 16 | 1450091822619F3F00C0E828 /* FetchedResultsControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008FB22619F3F00C0E828 /* FetchedResultsControllerDelegate.swift */; }; 17 | 1450091922619F3F00C0E828 /* FetchRequestController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008FD22619F3F00C0E828 /* FetchRequestController+Extensions.swift */; }; 18 | 1450091A22619F3F00C0E828 /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008FE22619F3F00C0E828 /* NSTableView+Extensions.swift */; }; 19 | 1450091B22619F3F00C0E828 /* ManagedObjectContextType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 145008FF22619F3F00C0E828 /* ManagedObjectContextType.swift */; }; 20 | 1450091C22619F3F00C0E828 /* NSArrayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090022619F3F00C0E828 /* NSArrayController+Extensions.swift */; }; 21 | 1450091D22619F4000C0E828 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090122619F3F00C0E828 /* UITableView+Extensions.swift */; }; 22 | 1450091E22619F4000C0E828 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090222619F3F00C0E828 /* UICollectionView+Extensions.swift */; }; 23 | 1450091F22619F4000C0E828 /* EntityObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090322619F3F00C0E828 /* EntityObserver.swift */; }; 24 | 1450092022619F4000C0E828 /* NSCollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090422619F3F00C0E828 /* NSCollectionView+Extensions.swift */; }; 25 | 1450092122619F4000C0E828 /* CustomPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090722619F3F00C0E828 /* CustomPersistentContainer.swift */; }; 26 | 1450092222619F4000C0E828 /* PersistentContainerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090822619F3F00C0E828 /* PersistentContainerType.swift */; }; 27 | 1450092322619F4000C0E828 /* PersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090922619F3F00C0E828 /* PersistentContainer.swift */; }; 28 | 1450092422619F4000C0E828 /* PersistentContainerAuxiliarTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090A22619F3F00C0E828 /* PersistentContainerAuxiliarTypes.swift */; }; 29 | 1450092522619F4000C0E828 /* ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090B22619F3F00C0E828 /* ManagedObjectContext.swift */; }; 30 | 1450092622619F4000C0E828 /* ManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090C22619F3F00C0E828 /* ManagedObject.swift */; }; 31 | 1450092722619F4000C0E828 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090E22619F3F00C0E828 /* Query.swift */; }; 32 | 1450092822619F4000C0E828 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450090F22619F3F00C0E828 /* Expression.swift */; }; 33 | 1450092922619F4000C0E828 /* FetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091022619F3F00C0E828 /* FetchRequest.swift */; }; 34 | 1450092A22619F4000C0E828 /* Predicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091122619F3F00C0E828 /* Predicate.swift */; }; 35 | 1450092B22619F4000C0E828 /* SortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091222619F3F00C0E828 /* SortDescriptor.swift */; }; 36 | 1450092C22619F4000C0E828 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091322619F3F00C0E828 /* Config.swift */; }; 37 | 1450092D22619F4000C0E828 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091422619F3F00C0E828 /* KeyPath.swift */; }; 38 | 1450092E22619F4000C0E828 /* Queryable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1450091522619F3F00C0E828 /* Queryable.swift */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXContainerItemProxy section */ 42 | 145008DF2261881400C0E828 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 145008CB2261881300C0E828 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 145008D32261881300C0E828; 47 | remoteInfo = AlecrimCoreData; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 145008D42261881300C0E828 /* AlecrimCoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimCoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 145008D72261881300C0E828 /* AlecrimCoreData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimCoreData.h; sourceTree = ""; }; 54 | 145008D82261881300C0E828 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 145008DD2261881300C0E828 /* AlecrimCoreDataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AlecrimCoreDataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 145008E22261881400C0E828 /* AlecrimCoreDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlecrimCoreDataTests.swift; sourceTree = ""; }; 57 | 145008E42261881400C0E828 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 145008EF22618CA700C0E828 /* AlecrimCoreData.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = AlecrimCoreData.xcconfig; sourceTree = ""; }; 59 | 145008F222618DF700C0E828 /* AlecrimCoreData.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = AlecrimCoreData.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 60 | 145008F322618DF700C0E828 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; 61 | 145008F422618DF700C0E828 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 62 | 145008F522618DF700C0E828 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 63 | 145008F622618DF700C0E828 /* AlecrimCoreData.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AlecrimCoreData.png; sourceTree = ""; }; 64 | 145008F722618E3100C0E828 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; 65 | 145008F922619F3F00C0E828 /* FetchRequestController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchRequestController.swift; sourceTree = ""; }; 66 | 145008FA22619F3F00C0E828 /* FetchedResultsSectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchedResultsSectionInfo.swift; sourceTree = ""; }; 67 | 145008FB22619F3F00C0E828 /* FetchedResultsControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchedResultsControllerDelegate.swift; sourceTree = ""; }; 68 | 145008FD22619F3F00C0E828 /* FetchRequestController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FetchRequestController+Extensions.swift"; sourceTree = ""; }; 69 | 145008FE22619F3F00C0E828 /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 70 | 145008FF22619F3F00C0E828 /* ManagedObjectContextType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextType.swift; sourceTree = ""; }; 71 | 1450090022619F3F00C0E828 /* NSArrayController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSArrayController+Extensions.swift"; sourceTree = ""; }; 72 | 1450090122619F3F00C0E828 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; 73 | 1450090222619F3F00C0E828 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; 74 | 1450090322619F3F00C0E828 /* EntityObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityObserver.swift; sourceTree = ""; }; 75 | 1450090422619F3F00C0E828 /* NSCollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSCollectionView+Extensions.swift"; sourceTree = ""; }; 76 | 1450090722619F3F00C0E828 /* CustomPersistentContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPersistentContainer.swift; sourceTree = ""; }; 77 | 1450090822619F3F00C0E828 /* PersistentContainerType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentContainerType.swift; sourceTree = ""; }; 78 | 1450090922619F3F00C0E828 /* PersistentContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentContainer.swift; sourceTree = ""; }; 79 | 1450090A22619F3F00C0E828 /* PersistentContainerAuxiliarTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentContainerAuxiliarTypes.swift; sourceTree = ""; }; 80 | 1450090B22619F3F00C0E828 /* ManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectContext.swift; sourceTree = ""; }; 81 | 1450090C22619F3F00C0E828 /* ManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObject.swift; sourceTree = ""; }; 82 | 1450090E22619F3F00C0E828 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; 83 | 1450090F22619F3F00C0E828 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 84 | 1450091022619F3F00C0E828 /* FetchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchRequest.swift; sourceTree = ""; }; 85 | 1450091122619F3F00C0E828 /* Predicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Predicate.swift; sourceTree = ""; }; 86 | 1450091222619F3F00C0E828 /* SortDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDescriptor.swift; sourceTree = ""; }; 87 | 1450091322619F3F00C0E828 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 88 | 1450091422619F3F00C0E828 /* KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = ""; }; 89 | 1450091522619F3F00C0E828 /* Queryable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queryable.swift; sourceTree = ""; }; 90 | /* End PBXFileReference section */ 91 | 92 | /* Begin PBXFrameworksBuildPhase section */ 93 | 145008D12261881300C0E828 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | 145008DA2261881300C0E828 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | 145008DE2261881400C0E828 /* AlecrimCoreData.framework in Frameworks */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXFrameworksBuildPhase section */ 109 | 110 | /* Begin PBXGroup section */ 111 | 145008CA2261881300C0E828 = { 112 | isa = PBXGroup; 113 | children = ( 114 | 145008D62261881300C0E828 /* Sources */, 115 | 145008EE2261898400C0E828 /* Supporting Files */, 116 | 145008E12261881400C0E828 /* Tests */, 117 | 145008D52261881300C0E828 /* Products */, 118 | 145008F122618D7C00C0E828 /* Metadata */, 119 | ); 120 | sourceTree = ""; 121 | }; 122 | 145008D52261881300C0E828 /* Products */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 145008D42261881300C0E828 /* AlecrimCoreData.framework */, 126 | 145008DD2261881300C0E828 /* AlecrimCoreDataTests.xctest */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | 145008D62261881300C0E828 /* Sources */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 1450090522619F3F00C0E828 /* Core */, 135 | 145008F822619F3F00C0E828 /* Fetch Request Controller */, 136 | 145008FC22619F3F00C0E828 /* Convenience */, 137 | ); 138 | path = Sources; 139 | sourceTree = ""; 140 | }; 141 | 145008E12261881400C0E828 /* Tests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 145008E22261881400C0E828 /* AlecrimCoreDataTests.swift */, 145 | 145008E42261881400C0E828 /* Info.plist */, 146 | ); 147 | path = Tests; 148 | sourceTree = ""; 149 | }; 150 | 145008EE2261898400C0E828 /* Supporting Files */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 145008D72261881300C0E828 /* AlecrimCoreData.h */, 154 | 145008EF22618CA700C0E828 /* AlecrimCoreData.xcconfig */, 155 | 145008D82261881300C0E828 /* Info.plist */, 156 | ); 157 | path = "Supporting Files"; 158 | sourceTree = ""; 159 | }; 160 | 145008F122618D7C00C0E828 /* Metadata */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 145008F622618DF700C0E828 /* AlecrimCoreData.png */, 164 | 145008F222618DF700C0E828 /* AlecrimCoreData.podspec */, 165 | 145008F722618E3100C0E828 /* CHANGELOG.md */, 166 | 145008F522618DF700C0E828 /* LICENSE */, 167 | 145008F422618DF700C0E828 /* Package.swift */, 168 | 145008F322618DF700C0E828 /* README.md */, 169 | ); 170 | name = Metadata; 171 | sourceTree = ""; 172 | }; 173 | 145008F822619F3F00C0E828 /* Fetch Request Controller */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 145008F922619F3F00C0E828 /* FetchRequestController.swift */, 177 | 145008FA22619F3F00C0E828 /* FetchedResultsSectionInfo.swift */, 178 | 145008FB22619F3F00C0E828 /* FetchedResultsControllerDelegate.swift */, 179 | ); 180 | path = "Fetch Request Controller"; 181 | sourceTree = ""; 182 | }; 183 | 145008FC22619F3F00C0E828 /* Convenience */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 145008FD22619F3F00C0E828 /* FetchRequestController+Extensions.swift */, 187 | 145008FE22619F3F00C0E828 /* NSTableView+Extensions.swift */, 188 | 145008FF22619F3F00C0E828 /* ManagedObjectContextType.swift */, 189 | 1450090022619F3F00C0E828 /* NSArrayController+Extensions.swift */, 190 | 1450090122619F3F00C0E828 /* UITableView+Extensions.swift */, 191 | 1450090222619F3F00C0E828 /* UICollectionView+Extensions.swift */, 192 | 1450090322619F3F00C0E828 /* EntityObserver.swift */, 193 | 1450090422619F3F00C0E828 /* NSCollectionView+Extensions.swift */, 194 | ); 195 | path = Convenience; 196 | sourceTree = ""; 197 | }; 198 | 1450090522619F3F00C0E828 /* Core */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 1450090622619F3F00C0E828 /* Persistent Container */, 202 | 1450090D22619F3F00C0E828 /* Query */, 203 | ); 204 | path = Core; 205 | sourceTree = ""; 206 | }; 207 | 1450090622619F3F00C0E828 /* Persistent Container */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 1450090722619F3F00C0E828 /* CustomPersistentContainer.swift */, 211 | 1450090822619F3F00C0E828 /* PersistentContainerType.swift */, 212 | 1450090922619F3F00C0E828 /* PersistentContainer.swift */, 213 | 1450090A22619F3F00C0E828 /* PersistentContainerAuxiliarTypes.swift */, 214 | 1450090B22619F3F00C0E828 /* ManagedObjectContext.swift */, 215 | 1450090C22619F3F00C0E828 /* ManagedObject.swift */, 216 | ); 217 | path = "Persistent Container"; 218 | sourceTree = ""; 219 | }; 220 | 1450090D22619F3F00C0E828 /* Query */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 1450090E22619F3F00C0E828 /* Query.swift */, 224 | 1450090F22619F3F00C0E828 /* Expression.swift */, 225 | 1450091022619F3F00C0E828 /* FetchRequest.swift */, 226 | 1450091122619F3F00C0E828 /* Predicate.swift */, 227 | 1450091222619F3F00C0E828 /* SortDescriptor.swift */, 228 | 1450091322619F3F00C0E828 /* Config.swift */, 229 | 1450091422619F3F00C0E828 /* KeyPath.swift */, 230 | 1450091522619F3F00C0E828 /* Queryable.swift */, 231 | ); 232 | path = Query; 233 | sourceTree = ""; 234 | }; 235 | /* End PBXGroup section */ 236 | 237 | /* Begin PBXHeadersBuildPhase section */ 238 | 145008CF2261881300C0E828 /* Headers */ = { 239 | isa = PBXHeadersBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | 145008E52261881400C0E828 /* AlecrimCoreData.h in Headers */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | /* End PBXHeadersBuildPhase section */ 247 | 248 | /* Begin PBXNativeTarget section */ 249 | 145008D32261881300C0E828 /* AlecrimCoreData */ = { 250 | isa = PBXNativeTarget; 251 | buildConfigurationList = 145008E82261881400C0E828 /* Build configuration list for PBXNativeTarget "AlecrimCoreData" */; 252 | buildPhases = ( 253 | 145008CF2261881300C0E828 /* Headers */, 254 | 145008D02261881300C0E828 /* Sources */, 255 | 145008D12261881300C0E828 /* Frameworks */, 256 | 145008D22261881300C0E828 /* Resources */, 257 | ); 258 | buildRules = ( 259 | ); 260 | dependencies = ( 261 | ); 262 | name = AlecrimCoreData; 263 | productName = AlecrimCoreData; 264 | productReference = 145008D42261881300C0E828 /* AlecrimCoreData.framework */; 265 | productType = "com.apple.product-type.framework"; 266 | }; 267 | 145008DC2261881300C0E828 /* AlecrimCoreDataTests */ = { 268 | isa = PBXNativeTarget; 269 | buildConfigurationList = 145008EB2261881400C0E828 /* Build configuration list for PBXNativeTarget "AlecrimCoreDataTests" */; 270 | buildPhases = ( 271 | 145008D92261881300C0E828 /* Sources */, 272 | 145008DA2261881300C0E828 /* Frameworks */, 273 | 145008DB2261881300C0E828 /* Resources */, 274 | ); 275 | buildRules = ( 276 | ); 277 | dependencies = ( 278 | 145008E02261881400C0E828 /* PBXTargetDependency */, 279 | ); 280 | name = AlecrimCoreDataTests; 281 | productName = AlecrimCoreDataTests; 282 | productReference = 145008DD2261881300C0E828 /* AlecrimCoreDataTests.xctest */; 283 | productType = "com.apple.product-type.bundle.unit-test"; 284 | }; 285 | /* End PBXNativeTarget section */ 286 | 287 | /* Begin PBXProject section */ 288 | 145008CB2261881300C0E828 /* Project object */ = { 289 | isa = PBXProject; 290 | attributes = { 291 | LastSwiftUpdateCheck = 1020; 292 | LastUpgradeCheck = 1020; 293 | ORGANIZATIONNAME = Alecrim; 294 | TargetAttributes = { 295 | 145008D32261881300C0E828 = { 296 | CreatedOnToolsVersion = 10.2; 297 | }; 298 | 145008DC2261881300C0E828 = { 299 | CreatedOnToolsVersion = 10.2; 300 | }; 301 | }; 302 | }; 303 | buildConfigurationList = 145008CE2261881300C0E828 /* Build configuration list for PBXProject "AlecrimCoreData" */; 304 | compatibilityVersion = "Xcode 10.0"; 305 | developmentRegion = en; 306 | hasScannedForEncodings = 0; 307 | knownRegions = ( 308 | en, 309 | Base, 310 | ); 311 | mainGroup = 145008CA2261881300C0E828; 312 | productRefGroup = 145008D52261881300C0E828 /* Products */; 313 | projectDirPath = ""; 314 | projectRoot = ""; 315 | targets = ( 316 | 145008D32261881300C0E828 /* AlecrimCoreData */, 317 | 145008DC2261881300C0E828 /* AlecrimCoreDataTests */, 318 | ); 319 | }; 320 | /* End PBXProject section */ 321 | 322 | /* Begin PBXResourcesBuildPhase section */ 323 | 145008D22261881300C0E828 /* Resources */ = { 324 | isa = PBXResourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | 145008F022618CA700C0E828 /* AlecrimCoreData.xcconfig in Resources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | 145008DB2261881300C0E828 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | /* End PBXResourcesBuildPhase section */ 339 | 340 | /* Begin PBXSourcesBuildPhase section */ 341 | 145008D02261881300C0E828 /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 1450092B22619F4000C0E828 /* SortDescriptor.swift in Sources */, 346 | 1450092922619F4000C0E828 /* FetchRequest.swift in Sources */, 347 | 1450092222619F4000C0E828 /* PersistentContainerType.swift in Sources */, 348 | 1450091F22619F4000C0E828 /* EntityObserver.swift in Sources */, 349 | 1450091B22619F3F00C0E828 /* ManagedObjectContextType.swift in Sources */, 350 | 1450092E22619F4000C0E828 /* Queryable.swift in Sources */, 351 | 1450092422619F4000C0E828 /* PersistentContainerAuxiliarTypes.swift in Sources */, 352 | 1450092D22619F4000C0E828 /* KeyPath.swift in Sources */, 353 | 1450092822619F4000C0E828 /* Expression.swift in Sources */, 354 | 1450091C22619F3F00C0E828 /* NSArrayController+Extensions.swift in Sources */, 355 | 1450092322619F4000C0E828 /* PersistentContainer.swift in Sources */, 356 | 1450091A22619F3F00C0E828 /* NSTableView+Extensions.swift in Sources */, 357 | 1450091D22619F4000C0E828 /* UITableView+Extensions.swift in Sources */, 358 | 1450092122619F4000C0E828 /* CustomPersistentContainer.swift in Sources */, 359 | 1450091922619F3F00C0E828 /* FetchRequestController+Extensions.swift in Sources */, 360 | 1450092722619F4000C0E828 /* Query.swift in Sources */, 361 | 1450092622619F4000C0E828 /* ManagedObject.swift in Sources */, 362 | 1450091E22619F4000C0E828 /* UICollectionView+Extensions.swift in Sources */, 363 | 1450091722619F3F00C0E828 /* FetchedResultsSectionInfo.swift in Sources */, 364 | 1450092A22619F4000C0E828 /* Predicate.swift in Sources */, 365 | 1450092C22619F4000C0E828 /* Config.swift in Sources */, 366 | 1450091822619F3F00C0E828 /* FetchedResultsControllerDelegate.swift in Sources */, 367 | 1450092022619F4000C0E828 /* NSCollectionView+Extensions.swift in Sources */, 368 | 1450092522619F4000C0E828 /* ManagedObjectContext.swift in Sources */, 369 | 1450091622619F3F00C0E828 /* FetchRequestController.swift in Sources */, 370 | ); 371 | runOnlyForDeploymentPostprocessing = 0; 372 | }; 373 | 145008D92261881300C0E828 /* Sources */ = { 374 | isa = PBXSourcesBuildPhase; 375 | buildActionMask = 2147483647; 376 | files = ( 377 | 145008E32261881400C0E828 /* AlecrimCoreDataTests.swift in Sources */, 378 | ); 379 | runOnlyForDeploymentPostprocessing = 0; 380 | }; 381 | /* End PBXSourcesBuildPhase section */ 382 | 383 | /* Begin PBXTargetDependency section */ 384 | 145008E02261881400C0E828 /* PBXTargetDependency */ = { 385 | isa = PBXTargetDependency; 386 | target = 145008D32261881300C0E828 /* AlecrimCoreData */; 387 | targetProxy = 145008DF2261881400C0E828 /* PBXContainerItemProxy */; 388 | }; 389 | /* End PBXTargetDependency section */ 390 | 391 | /* Begin XCBuildConfiguration section */ 392 | 145008E62261881400C0E828 /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | baseConfigurationReference = 145008EF22618CA700C0E828 /* AlecrimCoreData.xcconfig */; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_ANALYZER_NONNULL = YES; 398 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_ENABLE_OBJC_WEAK = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 409 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 410 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 411 | CLANG_WARN_EMPTY_BODY = YES; 412 | CLANG_WARN_ENUM_CONVERSION = YES; 413 | CLANG_WARN_INFINITE_RECURSION = YES; 414 | CLANG_WARN_INT_CONVERSION = YES; 415 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 417 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 419 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 420 | CLANG_WARN_STRICT_PROTOTYPES = YES; 421 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 422 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 423 | CLANG_WARN_UNREACHABLE_CODE = YES; 424 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 425 | COPY_PHASE_STRIP = NO; 426 | CURRENT_PROJECT_VERSION = 1; 427 | DEBUG_INFORMATION_FORMAT = dwarf; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | ENABLE_TESTABILITY = YES; 430 | GCC_C_LANGUAGE_STANDARD = gnu11; 431 | GCC_DYNAMIC_NO_PIC = NO; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_OPTIMIZATION_LEVEL = 0; 434 | GCC_PREPROCESSOR_DEFINITIONS = ( 435 | "DEBUG=1", 436 | "$(inherited)", 437 | ); 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 445 | MTL_FAST_MATH = YES; 446 | ONLY_ACTIVE_ARCH = YES; 447 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 449 | VERSIONING_SYSTEM = "apple-generic"; 450 | VERSION_INFO_PREFIX = ""; 451 | }; 452 | name = Debug; 453 | }; 454 | 145008E72261881400C0E828 /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | baseConfigurationReference = 145008EF22618CA700C0E828 /* AlecrimCoreData.xcconfig */; 457 | buildSettings = { 458 | ALWAYS_SEARCH_USER_PATHS = NO; 459 | CLANG_ANALYZER_NONNULL = YES; 460 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 462 | CLANG_CXX_LIBRARY = "libc++"; 463 | CLANG_ENABLE_MODULES = YES; 464 | CLANG_ENABLE_OBJC_ARC = YES; 465 | CLANG_ENABLE_OBJC_WEAK = YES; 466 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 467 | CLANG_WARN_BOOL_CONVERSION = YES; 468 | CLANG_WARN_COMMA = YES; 469 | CLANG_WARN_CONSTANT_CONVERSION = YES; 470 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 471 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 472 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 473 | CLANG_WARN_EMPTY_BODY = YES; 474 | CLANG_WARN_ENUM_CONVERSION = YES; 475 | CLANG_WARN_INFINITE_RECURSION = YES; 476 | CLANG_WARN_INT_CONVERSION = YES; 477 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 478 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 479 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 481 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 482 | CLANG_WARN_STRICT_PROTOTYPES = YES; 483 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 484 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 485 | CLANG_WARN_UNREACHABLE_CODE = YES; 486 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 487 | COPY_PHASE_STRIP = NO; 488 | CURRENT_PROJECT_VERSION = 1; 489 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 490 | ENABLE_NS_ASSERTIONS = NO; 491 | ENABLE_STRICT_OBJC_MSGSEND = YES; 492 | GCC_C_LANGUAGE_STANDARD = gnu11; 493 | GCC_NO_COMMON_BLOCKS = YES; 494 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 495 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 496 | GCC_WARN_UNDECLARED_SELECTOR = YES; 497 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 498 | GCC_WARN_UNUSED_FUNCTION = YES; 499 | GCC_WARN_UNUSED_VARIABLE = YES; 500 | MTL_ENABLE_DEBUG_INFO = NO; 501 | MTL_FAST_MATH = YES; 502 | SWIFT_COMPILATION_MODE = wholemodule; 503 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 504 | VERSIONING_SYSTEM = "apple-generic"; 505 | VERSION_INFO_PREFIX = ""; 506 | }; 507 | name = Release; 508 | }; 509 | 145008E92261881400C0E828 /* Debug */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | APPLICATION_EXTENSION_API_ONLY = YES; 513 | CODE_SIGN_IDENTITY = ""; 514 | CODE_SIGN_STYLE = Manual; 515 | COMBINE_HIDPI_IMAGES = YES; 516 | CURRENT_PROJECT_VERSION = 1842; 517 | DEFINES_MODULE = YES; 518 | DEVELOPMENT_TEAM = ""; 519 | DYLIB_COMPATIBILITY_VERSION = 1; 520 | DYLIB_CURRENT_VERSION = 1; 521 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 522 | FRAMEWORK_VERSION = A; 523 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 524 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 525 | LD_RUNPATH_SEARCH_PATHS = ( 526 | "$(inherited)", 527 | "@executable_path/../Frameworks", 528 | "@loader_path/Frameworks", 529 | ); 530 | PRODUCT_BUNDLE_IDENTIFIER = com.alecrim.AlecrimCoreData; 531 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 532 | PROVISIONING_PROFILE_SPECIFIER = ""; 533 | SKIP_INSTALL = YES; 534 | }; 535 | name = Debug; 536 | }; 537 | 145008EA2261881400C0E828 /* Release */ = { 538 | isa = XCBuildConfiguration; 539 | buildSettings = { 540 | APPLICATION_EXTENSION_API_ONLY = YES; 541 | CODE_SIGN_IDENTITY = ""; 542 | CODE_SIGN_STYLE = Manual; 543 | COMBINE_HIDPI_IMAGES = YES; 544 | CURRENT_PROJECT_VERSION = 1842; 545 | DEFINES_MODULE = YES; 546 | DEVELOPMENT_TEAM = ""; 547 | DYLIB_COMPATIBILITY_VERSION = 1; 548 | DYLIB_CURRENT_VERSION = 1; 549 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 550 | FRAMEWORK_VERSION = A; 551 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 552 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 553 | LD_RUNPATH_SEARCH_PATHS = ( 554 | "$(inherited)", 555 | "@executable_path/../Frameworks", 556 | "@loader_path/Frameworks", 557 | ); 558 | PRODUCT_BUNDLE_IDENTIFIER = com.alecrim.AlecrimCoreData; 559 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 560 | PROVISIONING_PROFILE_SPECIFIER = ""; 561 | SKIP_INSTALL = YES; 562 | }; 563 | name = Release; 564 | }; 565 | 145008EC2261881400C0E828 /* Debug */ = { 566 | isa = XCBuildConfiguration; 567 | buildSettings = { 568 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 569 | CODE_SIGN_STYLE = Manual; 570 | COMBINE_HIDPI_IMAGES = YES; 571 | DEVELOPMENT_TEAM = ""; 572 | INFOPLIST_FILE = Tests/Info.plist; 573 | LD_RUNPATH_SEARCH_PATHS = ( 574 | "$(inherited)", 575 | "@executable_path/../Frameworks", 576 | "@loader_path/../Frameworks", 577 | ); 578 | PRODUCT_BUNDLE_IDENTIFIER = com.alecrim.AlecrimCoreDataTests; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | PROVISIONING_PROFILE_SPECIFIER = ""; 581 | }; 582 | name = Debug; 583 | }; 584 | 145008ED2261881400C0E828 /* Release */ = { 585 | isa = XCBuildConfiguration; 586 | buildSettings = { 587 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 588 | CODE_SIGN_STYLE = Manual; 589 | COMBINE_HIDPI_IMAGES = YES; 590 | DEVELOPMENT_TEAM = ""; 591 | INFOPLIST_FILE = Tests/Info.plist; 592 | LD_RUNPATH_SEARCH_PATHS = ( 593 | "$(inherited)", 594 | "@executable_path/../Frameworks", 595 | "@loader_path/../Frameworks", 596 | ); 597 | PRODUCT_BUNDLE_IDENTIFIER = com.alecrim.AlecrimCoreDataTests; 598 | PRODUCT_NAME = "$(TARGET_NAME)"; 599 | PROVISIONING_PROFILE_SPECIFIER = ""; 600 | }; 601 | name = Release; 602 | }; 603 | /* End XCBuildConfiguration section */ 604 | 605 | /* Begin XCConfigurationList section */ 606 | 145008CE2261881300C0E828 /* Build configuration list for PBXProject "AlecrimCoreData" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 145008E62261881400C0E828 /* Debug */, 610 | 145008E72261881400C0E828 /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | 145008E82261881400C0E828 /* Build configuration list for PBXNativeTarget "AlecrimCoreData" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | 145008E92261881400C0E828 /* Debug */, 619 | 145008EA2261881400C0E828 /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | 145008EB2261881400C0E828 /* Build configuration list for PBXNativeTarget "AlecrimCoreDataTests" */ = { 625 | isa = XCConfigurationList; 626 | buildConfigurations = ( 627 | 145008EC2261881400C0E828 /* Debug */, 628 | 145008ED2261881400C0E828 /* Release */, 629 | ); 630 | defaultConfigurationIsVisible = 0; 631 | defaultConfigurationName = Release; 632 | }; 633 | /* End XCConfigurationList section */ 634 | }; 635 | rootObject = 145008CB2261881300C0E828 /* Project object */; 636 | } 637 | -------------------------------------------------------------------------------- /AlecrimCoreData.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AlecrimCoreData.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AlecrimCoreData.xcodeproj/xcshareddata/xcschemes/AlecrimCoreData.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # AlecrimCoreData 2 | 3 | ## 7.0-beta.1 4 | 2019-04-13 5 | 6 | - New project structure; 7 | - Swift 5.0 compatible. 8 | 9 | 10 | ## 6.0.1 11 | 2018-06-29 12 | 13 | - Some refactoring; 14 | - Bug fixes and improvements.. 15 | 16 | ## 6.0 17 | 2018-05-20 18 | 19 | - New version wrote from the ground. 20 | 21 | ## 5.2 22 | 2018-03-07 23 | 24 | - Converted to Swift 4. 25 | 26 | ## 5.1 27 | 2018-01-24 28 | 29 | - Swift 3.2 compatible; 30 | - Xcode 9 compatible. 31 | 32 | ## 5.0 33 | 2016-12-30 34 | 35 | - Swift 3 compatible version. 36 | 37 | ## 4.2.5 38 | 2016-10-06 39 | 40 | - Last Swift 2.3 compatible version. 41 | 42 | ## 4.2.4 43 | 2016-10-06 44 | 45 | - Minor fixes. 46 | 47 | ## 4.2.3 48 | 2016-09-08 49 | 50 | - Minor fixes. 51 | 52 | ## 4.2.2 53 | 2016-09-08 54 | 55 | - Bug fixes and improvements. 56 | 57 | ## 4.2.1 58 | 2016-08-16 59 | 60 | - Last Swift 2.2 compatible version. 61 | 62 | ## 4.2 63 | 2016-08-13 64 | 65 | - Bug fixes and improvements. 66 | 67 | ## 4.1.1 68 | 2016-02-22 69 | 70 | - Minor fixes. 71 | 72 | ## 4.1 73 | 2016-02-22 74 | 75 | - Added some ACDGen options; 76 | - Other bug fixes. 77 | 78 | ## 4.0.6 79 | 2016-02-02 80 | 81 | - Fixed entity description finding and caching. 82 | 83 | ## 4.0.5 84 | 2016-02-02 85 | 86 | - Minor fixes. 87 | 88 | ## 4.0.4 89 | 2016-01-31 90 | 91 | - Bug fixes and improvements. 92 | 93 | ## 4.0.3 94 | 2015-12-04 95 | 96 | - Bug fixes and improvements. 97 | 98 | ## 4.0.2 99 | 2015-11-07 100 | 101 | - Minor fixes. 102 | 103 | ## 4.0.1 104 | 2015-11-07 105 | 106 | - Minor fixes. 107 | 108 | ## 4.0 109 | 2015-11-07 110 | 111 | - New internal architecture; 112 | - Compatible with Swift 2.x; 113 | - ACDGen source code is now part of AlecrimCoreData; 114 | - Other improvements and fixes. 115 | 116 | ## 3.3.1 117 | 2015-08-05 118 | 119 | - Minor podspec fix. 120 | 121 | ## 3.3 122 | 2015-08-05 123 | 124 | - Recreated data context management; 125 | - `Context` is now a `NSManagedObjectContext` subclass; 126 | - Improvements and important bug fixes. 127 | 128 | ## 3.2 129 | 2015-06-30 130 | 131 | - Added `ALCFetchedResultsController` (`NSFetchedResultsController` version for OS X); 132 | - Improvements; 133 | - Fixes. 134 | 135 | ## 3.1 136 | 2015-06-05 137 | 138 | - Added new `EntitySetAttribute` functionalities; 139 | - Better `NSAsynchronousFetchRequest` handling; 140 | - Improvements and fixes. 141 | 142 | ## 3.0.1 143 | 2015-05-25 144 | 145 | - Better handling for merging background context changes. 146 | 147 | ## 3.0 148 | 2015-05-20 149 | 150 | - Added attributes support and many other improvements. 151 | 152 | ## 2.1 153 | 2015-04-04 154 | 155 | - Added CocoaPods and Carthage support. 156 | 157 | ## 2.0 158 | 2015-02-16 159 | 160 | - First public stable version written in Swift. 161 | 162 | ## 2.0-beta.1 163 | 2014-08-03 164 | 165 | - First public beta version written in Swift. 166 | 167 | ## 2.0-alpha.1 168 | 2014-06-24 169 | 170 | - First internal version written in Swift. 171 | 172 | ## 1.x 173 | 2013-10-15 174 | 175 | - Internal versions written in Objective-C. 176 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2019 Vanderlei Martinelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "AlecrimCoreData", 6 | platforms: [ 7 | .macOS(.v10_12), 8 | .iOS(.v10), 9 | .watchOS(.v3), 10 | .tvOS(.v10) 11 | ], 12 | products: [ 13 | .library(name: "AlecrimCoreData", targets: ["AlecrimCoreData"]) 14 | ], 15 | targets: [ 16 | .target(name: "AlecrimCoreData", path: "Sources") 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![AlecrimCoreData](https://raw.githubusercontent.com/Alecrim/AlecrimCoreData/master/AlecrimCoreData.png) 2 | 3 | [![Version](https://img.shields.io/badge/v7.0%20beta%201-blue.svg?label=version&style=flat)](https://github.com/Alecrim/AlecrimCoreData) 4 | [![Language: swift](https://img.shields.io/badge/swift-v5.0-blue.svg?style=flat)](https://developer.apple.com/swift/) 5 | [![Platforms](https://img.shields.io/badge/platforms-macOS%2C%20iOS%2C%20watchOS%2C%20tvOS-blue.svg?style=flat)](http://cocoadocs.org/docsets/AlecrimCoreData) 6 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/Alecrim/AlecrimCoreData/develop/LICENSE) 7 | [![Author: Vanderlei Martinelli](https://img.shields.io/badge/author-Vanderlei%20Martinelli-blue.svg?style=flat)](https://www.linkedin.com/in/vmartinelli) 8 | 9 | A powerful and elegant Core Data framework for Swift. 10 | 11 | ## Usage 12 | 13 | ### Beta version. New docs soon... 14 | 15 | Simple do that: 16 | 17 | ```swift 18 | let query = persistentContainer.viewContext.people 19 | .where { \.city == "Piracicaba" } 20 | .orderBy { \.name } 21 | 22 | for person in query.dropFirst(20).prefix(10) { 23 | print(person.name, person.address) 24 | } 25 | ``` 26 | 27 | Or that: 28 | 29 | ```swift 30 | persistentContainer.performBackgroundTask { context in 31 | let query = context.people 32 | .filtered(using: \.country == "Brazil" && \.isContributor == true) 33 | .sorted(by: .descending(\.contributionCount)) 34 | .sorted(by: \.name) 35 | 36 | if let person = query.first() { 37 | print(person.name, person.email) 38 | } 39 | } 40 | ``` 41 | 42 | After that: 43 | 44 | ```swift 45 | import AlecrimCoreData 46 | 47 | extension ManagedObjectContext { 48 | var people: Query { return Query(in: self) } 49 | } 50 | 51 | let persistentContainer = PersistentContainer() 52 | 53 | ``` 54 | And after your have created your matching managed object model in Xcode, of course. ;-) 55 | 56 | 57 | ## Contribute 58 | If you have any problems or need more information, please open an issue using the provided GitHub link. 59 | 60 | You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submitted patches. 61 | 62 | - master - The production branch. Clone or fork this repository for the latest copy. 63 | - develop - The active development branch. [Pull requests](https://help.github.com/articles/creating-a-pull-request) should be directed to this branch. 64 | 65 | 66 | ## Contact the author 67 | - [Vanderlei Martinelli](https://www.linkedin.com/in/vmartinelli) 68 | 69 | ## License 70 | **AlecrimCoreData** is released under an MIT license. See LICENSE for more information. 71 | -------------------------------------------------------------------------------- /Sources/Convenience/EntityObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EntityObserver.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 07/06/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - 12 | 13 | /// A fetch request controller wrapper for one entity only. Can be used as observer for an entity detail presentation, for example. 14 | public final class EntityObserver { 15 | 16 | private let frc: FetchRequestController 17 | 18 | fileprivate init(entity: EntityType, propertyName: String, updateHandler didChangeContentClosure: @escaping () -> Void, context: ManagedObjectContext) { 19 | self.frc = Query(in: context) 20 | .batchSize(0) 21 | .filtered(using: Predicate(format: "SELF == %@", argumentArray: [entity])) 22 | .sorted(by: SortDescriptor(key: propertyName, ascending: true)) 23 | .toFetchRequestController() 24 | 25 | self.frc.didChangeContent(closure: didChangeContentClosure) 26 | } 27 | 28 | deinit { 29 | self.frc.removeAllBindings() 30 | } 31 | 32 | } 33 | 34 | // MARK: - 35 | 36 | extension PersistentContainerType { 37 | public func observer(for entity: EntityType, updateHandler: @escaping () -> Void) -> EntityObserver { 38 | // using any property here is fine, but there must be at least one property 39 | guard let propertyName = entity.entity.properties.first(where: { $0.isTransient == false })?.name else { 40 | fatalError("No property found.") 41 | } 42 | 43 | return EntityObserver(entity: entity, propertyName: propertyName, updateHandler: updateHandler, context: self.viewContext) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/Convenience/FetchRequestController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchRequestController+Extensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 21/04/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FetchRequestController { 12 | 13 | internal enum Change { 14 | case insert(T) 15 | case delete(T) 16 | case update(T) 17 | case move(T, T) // from, to 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Sources/Convenience/ManagedObjectContextType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManagedObjectContextType.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 07/06/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - 12 | 13 | extension ManagedObjectContext: ManagedObjectContextType {} 14 | 15 | // MARK: - 16 | 17 | public protocol ManagedObjectContextType { 18 | func perform(_ block: @escaping () -> Void) 19 | func performAndWait(_ block: () -> Void) 20 | } 21 | 22 | // MARK: - 23 | 24 | extension ManagedObjectContextType { 25 | 26 | public func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { 27 | let context = self 28 | 29 | context.perform { 30 | do { 31 | let value = try closure(context) 32 | completion(value, nil) 33 | } 34 | catch { 35 | completion(nil, error) 36 | } 37 | } 38 | } 39 | 40 | public func async(execute closure: @escaping (Self) -> Value, completion: ((Value) -> Void)? = nil) { 41 | let context = self 42 | 43 | context.perform { 44 | let value = closure(context) 45 | completion?(value) 46 | } 47 | } 48 | 49 | @discardableResult 50 | public func sync(execute closure: (Self) throws -> Value) throws -> Value { 51 | var value: Value? 52 | var outError: Error? 53 | 54 | let context = self 55 | 56 | context.performAndWait { 57 | do { 58 | value = try closure(context) 59 | } 60 | catch { 61 | outError = error 62 | } 63 | } 64 | 65 | if let outError = outError { 66 | throw outError 67 | } 68 | 69 | return value! 70 | } 71 | 72 | @discardableResult 73 | public func sync(execute closure: (Self) -> Value) -> Value { 74 | var value: Value? 75 | 76 | let context = self 77 | 78 | context.performAndWait { 79 | value = closure(context) 80 | } 81 | 82 | return value! 83 | } 84 | 85 | } 86 | 87 | // MARK: - 88 | 89 | extension PersistentContainer { 90 | 91 | public func async(execute closure: @escaping (ManagedObjectContext) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { 92 | return self.backgroundContext.async(execute: closure, completion: completion) 93 | } 94 | 95 | public func async(execute closure: @escaping (ManagedObjectContext) -> Value, completion: ((Value) -> Void)? = nil) { 96 | return self.backgroundContext.async(execute: closure, completion: completion) 97 | } 98 | 99 | @discardableResult 100 | public func sync(execute closure: (ManagedObjectContext) throws -> Value) throws -> Value { 101 | return try self.backgroundContext.sync(execute: closure) 102 | } 103 | 104 | @discardableResult 105 | public func sync(execute closure: (ManagedObjectContext) -> Value) -> Value { 106 | return self.backgroundContext.sync(execute: closure) 107 | } 108 | 109 | } 110 | 111 | // MARK: - 112 | 113 | extension CustomPersistentContainer { 114 | 115 | public func async(execute closure: @escaping (Context) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { 116 | return self.backgroundContext.async(execute: closure, completion: completion) 117 | } 118 | 119 | public func async(execute closure: @escaping (Context) -> Value, completion: ((Value) -> Void)? = nil) { 120 | return self.backgroundContext.async(execute: closure, completion: completion) 121 | } 122 | 123 | @discardableResult 124 | public func sync(execute closure: (Context) throws -> Value) throws -> Value { 125 | return try self.backgroundContext.sync(execute: closure) 126 | } 127 | 128 | @discardableResult 129 | public func sync(execute closure: (Context) -> Value) -> Value { 130 | return self.backgroundContext.sync(execute: closure) 131 | } 132 | 133 | } 134 | 135 | -------------------------------------------------------------------------------- /Sources/Convenience/NSArrayController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSArrayController+Extensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 2015-07-28. 6 | // Copyright (c) 2015 Alecrim. All rights reserved. 7 | // 8 | 9 | #if os(macOS) 10 | 11 | import Foundation 12 | import AppKit 13 | 14 | extension Query { 15 | 16 | public func toArrayController() -> NSArrayController { 17 | let fetchRequest = self.fetchRequest.toRaw() as NSFetchRequest 18 | 19 | let arrayController = NSArrayController(content: nil) 20 | 21 | arrayController.managedObjectContext = self.context 22 | arrayController.entityName = fetchRequest.entityName 23 | 24 | arrayController.fetchPredicate = fetchRequest.predicate 25 | if let sortDescriptors = fetchRequest.sortDescriptors { 26 | arrayController.sortDescriptors = sortDescriptors 27 | } 28 | 29 | arrayController.automaticallyPreparesContent = true 30 | arrayController.automaticallyRearrangesObjects = true 31 | arrayController.usesLazyFetching = true 32 | 33 | return arrayController 34 | } 35 | 36 | } 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /Sources/Convenience/NSCollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionView+Extensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 2015-07-28. 6 | // Copyright (c) 2015 Alecrim. All rights reserved. 7 | // 8 | 9 | #if os(macOS) 10 | 11 | import Foundation 12 | import AppKit 13 | 14 | extension FetchRequestController { 15 | 16 | /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. 17 | @discardableResult 18 | public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, animated: Bool = false, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { 19 | // 20 | var reloadData = false 21 | var sectionChanges = Array>() 22 | var itemChanges = Array>() 23 | 24 | // 25 | func reset() { 26 | reloadData = false 27 | sectionChanges.removeAll() 28 | itemChanges.removeAll() 29 | } 30 | 31 | // 32 | self 33 | .needsReloadData { 34 | reloadData = true 35 | } 36 | .willChangeContent { 37 | if collectionView.numberOfSections == 0 { 38 | reloadData = true 39 | } 40 | 41 | guard !reloadData else { return } 42 | reset() 43 | } 44 | .didInsertSection { _, sectionIndex in 45 | guard !reloadData else { return } 46 | sectionChanges.append(.insert(sectionIndex)) 47 | } 48 | .didDeleteSection { _, sectionIndex in 49 | guard !reloadData else { return } 50 | sectionChanges.append(.delete(sectionIndex)) 51 | } 52 | .didInsertObject { _, newIndexPath in 53 | guard !reloadData else { return } 54 | 55 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 56 | itemChanges.append(.insert(newIndexPath)) 57 | } 58 | .didDeleteObject { _, indexPath in 59 | guard !reloadData else { return } 60 | 61 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 62 | itemChanges.append(.delete(indexPath)) 63 | } 64 | .didUpdateObject { _, indexPath in 65 | guard !reloadData else { return } 66 | 67 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 68 | itemChanges.append(.update(indexPath)) 69 | } 70 | .didMoveObject { entity, indexPath, newIndexPath in 71 | guard !reloadData else { return } 72 | 73 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 74 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 75 | 76 | itemChanges.append(.move(indexPath, newIndexPath)) 77 | } 78 | .didChangeContent { [weak collectionView] in 79 | // 80 | guard let collectionView = collectionView else { 81 | reset() 82 | return 83 | } 84 | 85 | // 86 | guard !reloadData else { 87 | collectionView.reloadData() 88 | reset() 89 | return 90 | } 91 | 92 | // 93 | var updatedIndexPaths = [IndexPath]() 94 | 95 | let performer = animated ? collectionView.animator() : collectionView 96 | 97 | performer.performBatchUpdates({ 98 | sectionChanges.forEach { 99 | switch $0 { 100 | case .insert(let sectionIndex): 101 | collectionView.insertSections(IndexSet(integer: sectionIndex)) 102 | 103 | case .delete(let sectionIndex): 104 | collectionView.deleteSections(IndexSet(integer: sectionIndex)) 105 | 106 | default: 107 | break 108 | } 109 | } 110 | 111 | itemChanges.forEach { 112 | switch $0 { 113 | case .insert(let indexPath): 114 | collectionView.insertItems(at: Set([indexPath])) 115 | 116 | case .delete(let indexPath): 117 | collectionView.deleteItems(at: Set([indexPath])) 118 | 119 | case .update(let indexPath): 120 | if itemConfigurationHandler == nil { 121 | collectionView.reloadItems(at: Set([indexPath])) 122 | } 123 | else { 124 | updatedIndexPaths.append(indexPath) 125 | } 126 | 127 | case .move(let oldIndexPath, let newIndexPath): 128 | collectionView.moveItem(at: oldIndexPath, to: newIndexPath) 129 | 130 | // workaround to be sure that cells will be refreshed 131 | // note: this only works when using a cell configuration handler 132 | if itemConfigurationHandler != nil { 133 | updatedIndexPaths.append(newIndexPath) 134 | } 135 | } 136 | } 137 | }, completionHandler: { _ in 138 | updatedIndexPaths.forEach { 139 | if let item = collectionView.item(at: $0) { 140 | itemConfigurationHandler?(item, $0) 141 | } 142 | } 143 | 144 | reset() 145 | }) 146 | } 147 | 148 | // 149 | collectionView.reloadData() 150 | 151 | // 152 | return self 153 | } 154 | 155 | } 156 | 157 | #endif 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /Sources/Convenience/NSTableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+Extensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 04/04/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | #if os(macOS) 10 | 11 | import Foundation 12 | import Cocoa 13 | 14 | extension FetchRequestController { 15 | 16 | /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. 17 | @discardableResult 18 | public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, animated: Bool = false, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { 19 | // 20 | var reloadData = false 21 | var sectionChanges = Array>() 22 | var itemChanges = Array>() 23 | 24 | // 25 | func reset() { 26 | reloadData = false 27 | sectionChanges.removeAll() 28 | itemChanges.removeAll() 29 | } 30 | 31 | // 32 | self 33 | .needsReloadData { 34 | reloadData = true 35 | } 36 | .willChangeContent { 37 | if tableView.numberOfRows == 0 { 38 | reloadData = true 39 | } 40 | 41 | guard !reloadData else { return } 42 | reset() 43 | } 44 | .didInsertSection { _, sectionIndex in 45 | reloadData = true 46 | } 47 | .didDeleteSection { _, sectionIndex in 48 | reloadData = true 49 | } 50 | .didInsertObject { _, newIndexPath in 51 | guard !reloadData else { return } 52 | 53 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 54 | itemChanges.append(.insert(newIndexPath)) 55 | } 56 | .didDeleteObject { _, indexPath in 57 | guard !reloadData else { return } 58 | 59 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 60 | itemChanges.append(.delete(indexPath)) 61 | } 62 | .didUpdateObject { _, indexPath in 63 | guard !reloadData else { return } 64 | 65 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 66 | itemChanges.append(.update(indexPath)) 67 | } 68 | .didMoveObject { entity, indexPath, newIndexPath in 69 | guard !reloadData else { return } 70 | 71 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 72 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 73 | 74 | itemChanges.append(.move(indexPath, newIndexPath)) 75 | } 76 | .didChangeContent { [weak tableView] in 77 | // 78 | defer { reset() } 79 | 80 | // 81 | guard let tableView = tableView else { 82 | return 83 | } 84 | 85 | // 86 | guard !reloadData else { 87 | tableView.reloadData() 88 | return 89 | } 90 | 91 | // 92 | let performer = animated ? tableView.animator() : tableView 93 | 94 | // 95 | performer.beginUpdates() 96 | 97 | // 98 | var updatedIndexPaths = [IndexPath]() 99 | 100 | itemChanges.forEach { 101 | switch $0 { 102 | case .update(let indexPath): 103 | if cellViewConfigurationHandler == nil { 104 | performer.reloadData(forRowIndexes: IndexSet(integer: indexPath.item), columnIndexes: IndexSet()) 105 | } 106 | else { 107 | updatedIndexPaths.append(indexPath) 108 | } 109 | 110 | case .delete(let indexPath): 111 | performer.removeRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) 112 | 113 | case .insert(let indexPath): 114 | performer.insertRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) 115 | 116 | case .move(let oldIndexPath, let newIndexPath): 117 | //performer.moveRow(at: oldIndexPath.item, to: newIndexPath.item) 118 | performer.removeRows(at: IndexSet(integer: oldIndexPath.item), withAnimation: animationOptions) 119 | performer.insertRows(at: IndexSet(integer: newIndexPath.item), withAnimation: animationOptions) 120 | } 121 | } 122 | 123 | // 124 | performer.endUpdates() 125 | 126 | // 127 | updatedIndexPaths.forEach { 128 | if let item = tableView.view(atColumn: 0, row: $0.item, makeIfNecessary: false) as? NSTableCellView { 129 | cellViewConfigurationHandler?(item, $0) 130 | } 131 | } 132 | } 133 | 134 | // 135 | tableView.reloadData() 136 | 137 | // 138 | return self 139 | } 140 | 141 | } 142 | 143 | #endif 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /Sources/Convenience/UICollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewExtensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 2015-07-28. 6 | // Copyright (c) 2015 Alecrim. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | 11 | import Foundation 12 | import UIKit 13 | 14 | extension FetchRequestController { 15 | 16 | /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. 17 | @discardableResult 18 | public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((UICollectionViewCell, IndexPath) -> Void)? = nil) -> Self { 19 | // 20 | var reloadData = false 21 | var sectionChanges = Array>() 22 | var itemChanges = Array>() 23 | 24 | // 25 | func reset() { 26 | reloadData = false 27 | sectionChanges.removeAll() 28 | itemChanges.removeAll() 29 | } 30 | 31 | // 32 | self 33 | .needsReloadData { 34 | reloadData = true 35 | } 36 | .willChangeContent { 37 | if collectionView.numberOfSections == 0 { 38 | reloadData = true 39 | } 40 | 41 | guard !reloadData else { return } 42 | reset() 43 | } 44 | .didInsertSection { _, sectionIndex in 45 | guard !reloadData else { return } 46 | sectionChanges.append(.insert(sectionIndex)) 47 | } 48 | .didDeleteSection { _, sectionIndex in 49 | guard !reloadData else { return } 50 | sectionChanges.append(.delete(sectionIndex)) 51 | } 52 | .didInsertObject { _, newIndexPath in 53 | guard !reloadData else { return } 54 | 55 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 56 | itemChanges.append(.insert(newIndexPath)) 57 | } 58 | .didDeleteObject { _, indexPath in 59 | guard !reloadData else { return } 60 | 61 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 62 | itemChanges.append(.delete(indexPath)) 63 | } 64 | .didUpdateObject { _, indexPath in 65 | guard !reloadData else { return } 66 | 67 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 68 | itemChanges.append(.update(indexPath)) 69 | } 70 | .didMoveObject { entity, indexPath, newIndexPath in 71 | guard !reloadData else { return } 72 | 73 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 74 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 75 | 76 | itemChanges.append(.move(indexPath, newIndexPath)) 77 | } 78 | .didChangeContent { [weak collectionView] in 79 | guard let collectionView = collectionView else { 80 | reset() 81 | return 82 | } 83 | 84 | guard !reloadData else { 85 | collectionView.reloadData() 86 | reset() 87 | return 88 | } 89 | 90 | // 91 | var updatedIndexPaths = [IndexPath]() 92 | 93 | collectionView.performBatchUpdates({ 94 | sectionChanges.forEach { 95 | switch $0 { 96 | case .insert(let sectionIndex): 97 | collectionView.insertSections(IndexSet(integer: sectionIndex)) 98 | 99 | case .delete(let sectionIndex): 100 | collectionView.deleteSections(IndexSet(integer: sectionIndex)) 101 | 102 | default: 103 | break 104 | } 105 | } 106 | 107 | itemChanges.forEach { 108 | switch $0 { 109 | case .insert(let indexPath): 110 | collectionView.insertItems(at: [indexPath]) 111 | 112 | case .delete(let indexPath): 113 | collectionView.deleteItems(at: [indexPath]) 114 | 115 | case .update(let indexPath): 116 | if cellConfigurationHandler == nil { 117 | collectionView.reloadItems(at: [indexPath]) 118 | } 119 | else { 120 | updatedIndexPaths.append(indexPath) 121 | } 122 | 123 | case .move(let oldIndexPath, let newIndexPath): 124 | collectionView.moveItem(at: oldIndexPath, to: newIndexPath) 125 | 126 | // workaround to be sure that cells will be refreshed 127 | // note: this only works when using a cell configuration handler 128 | if cellConfigurationHandler != nil { 129 | updatedIndexPaths.append(newIndexPath) 130 | } 131 | } 132 | } 133 | }, completion: { _ in 134 | updatedIndexPaths.forEach { 135 | if let cell = collectionView.cellForItem(at: $0) { 136 | cellConfigurationHandler?(cell, $0) 137 | } 138 | } 139 | 140 | reset() 141 | }) 142 | } 143 | 144 | // 145 | collectionView.reloadData() 146 | 147 | // 148 | return self 149 | } 150 | 151 | } 152 | 153 | #endif 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /Sources/Convenience/UITableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extensions.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 2015-07-28. 6 | // Copyright (c) 2015 Alecrim. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | 11 | import Foundation 12 | import UIKit 13 | 14 | extension FetchRequestController { 15 | 16 | /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. 17 | @discardableResult 18 | public func bind(to tableView: UITableView, rowAnimation: UITableView.RowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((UITableViewCell, IndexPath) -> Void)? = nil) -> Self { 19 | // 20 | var reloadData = false 21 | var sectionChanges = Array>() 22 | var itemChanges = Array>() 23 | 24 | // 25 | func reset() { 26 | reloadData = false 27 | sectionChanges.removeAll() 28 | itemChanges.removeAll() 29 | } 30 | 31 | // 32 | self 33 | .needsReloadData { 34 | reloadData = true 35 | } 36 | .willChangeContent { 37 | if tableView.numberOfSections == 0 { 38 | reloadData = true 39 | } 40 | 41 | guard !reloadData else { return } 42 | reset() 43 | } 44 | .didInsertSection { _, sectionIndex in 45 | guard !reloadData else { return } 46 | sectionChanges.append(.insert(sectionIndex)) 47 | } 48 | .didDeleteSection { _, sectionIndex in 49 | guard !reloadData else { return } 50 | sectionChanges.append(.delete(sectionIndex)) 51 | } 52 | .didInsertObject { _, newIndexPath in 53 | guard !reloadData else { return } 54 | 55 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 56 | itemChanges.append(.insert(newIndexPath)) 57 | } 58 | .didDeleteObject { _, indexPath in 59 | guard !reloadData else { return } 60 | 61 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 62 | itemChanges.append(.delete(indexPath)) 63 | } 64 | .didUpdateObject { _, indexPath in 65 | guard !reloadData else { return } 66 | 67 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 68 | itemChanges.append(.update(indexPath)) 69 | } 70 | .didMoveObject { entity, indexPath, newIndexPath in 71 | guard !reloadData else { return } 72 | 73 | let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath 74 | let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath 75 | 76 | itemChanges.append(.move(indexPath, newIndexPath)) 77 | } 78 | .didChangeContent { [weak tableView] in 79 | // 80 | defer { reset() } 81 | 82 | // 83 | guard let tableView = tableView else { 84 | return 85 | } 86 | 87 | // 88 | guard !reloadData else { 89 | tableView.reloadData() 90 | return 91 | } 92 | 93 | // 94 | tableView.beginUpdates() 95 | 96 | // 97 | sectionChanges.forEach { 98 | switch $0 { 99 | case .delete(let sectionIndex): 100 | tableView.deleteSections(IndexSet(integer: sectionIndex), with: rowAnimation) 101 | 102 | case .insert(let sectionIndex): 103 | tableView.insertSections(IndexSet(integer: sectionIndex), with: rowAnimation) 104 | 105 | default: 106 | break 107 | } 108 | } 109 | 110 | // 111 | var updatedIndexPaths = [IndexPath]() 112 | 113 | itemChanges.forEach { 114 | switch $0 { 115 | case .update(let indexPath): 116 | if cellConfigurationHandler == nil { 117 | tableView.reloadRows(at: [indexPath], with: rowAnimation) 118 | } 119 | else { 120 | updatedIndexPaths.append(indexPath) 121 | } 122 | 123 | case .delete(let indexPath): 124 | tableView.deleteRows(at: [indexPath], with: rowAnimation) 125 | 126 | case .insert(let indexPath): 127 | tableView.insertRows(at: [indexPath], with: rowAnimation) 128 | 129 | case .move(let oldIndexPath, let newIndexPath): 130 | //tableView.moveRow(at: oldIndexPath, to: newIndexPath) 131 | tableView.deleteRows(at: [oldIndexPath], with: rowAnimation) 132 | tableView.insertRows(at: [newIndexPath], with: rowAnimation) 133 | } 134 | } 135 | 136 | // 137 | tableView.endUpdates() 138 | 139 | // 140 | updatedIndexPaths.forEach { 141 | if let item = tableView.cellForRow(at: $0) { 142 | cellConfigurationHandler?(item, $0) 143 | } 144 | } 145 | } 146 | 147 | // 148 | tableView.reloadData() 149 | 150 | // 151 | return self 152 | } 153 | 154 | } 155 | 156 | #endif 157 | 158 | 159 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/CustomPersistentContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPersistentContainer.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 20/05/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | open class CustomPersistentContainer { 13 | 14 | // MARK: - 15 | 16 | public private(set) lazy var backgroundContext: Context = self.newBackgroundContext() 17 | 18 | // MARK: - 19 | 20 | fileprivate let rawValue: NSPersistentContainer 21 | 22 | // MARK: - 23 | 24 | /// Use caution when using this initializer. 25 | public init(name: String? = nil) { 26 | self.rawValue = HelperPersistentContainer(name: name) 27 | } 28 | 29 | public init(name: String? = nil, managedObjectModel: NSManagedObjectModel, storageType: PersistentContainerStorageType, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { 30 | self.rawValue = try HelperPersistentContainer(name: name, managedObjectModel: managedObjectModel, storageType: storageType, persistentStoreURL: persistentStoreURL, persistentStoreDescriptionOptions: persistentStoreDescriptionOptions, ubiquitousConfiguration: ubiquitousConfiguration) 31 | } 32 | 33 | public init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { 34 | self.rawValue = try HelperPersistentContainer(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription, completionHandler: completionHandler) 35 | } 36 | 37 | // MARK: - 38 | 39 | open var viewContext: Context { 40 | return unsafeDowncast(self.rawValue.viewContext, to: Context.self) 41 | } 42 | 43 | open func newBackgroundContext() -> Context { 44 | return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) 45 | } 46 | 47 | open func performBackgroundTask(_ block: @escaping (Context) -> Void) { 48 | self.rawValue.performBackgroundTask { managedObjectContext in 49 | block(unsafeDowncast(managedObjectContext, to: Context.self)) 50 | } 51 | } 52 | 53 | } 54 | 55 | // MARK: - 56 | 57 | extension CustomPersistentContainer { 58 | 59 | fileprivate final class HelperPersistentContainer: PersistentContainer { 60 | 61 | private lazy var _viewContext: NSManagedObjectContext = { 62 | let context = Context(concurrencyType: .mainQueueConcurrencyType) 63 | self.configureManagedObjectContext(context) 64 | 65 | return context 66 | }() 67 | 68 | fileprivate override var viewContext: NSManagedObjectContext { return self._viewContext } 69 | 70 | fileprivate override func newBackgroundContext() -> NSManagedObjectContext { 71 | let context = Context(concurrencyType: .privateQueueConcurrencyType) 72 | self.configureManagedObjectContext(context) 73 | 74 | return context 75 | } 76 | 77 | fileprivate override func configureManagedObjectContext(_ context: NSManagedObjectContext) { 78 | context.persistentStoreCoordinator = self.persistentStoreCoordinator 79 | super.configureManagedObjectContext(context) 80 | } 81 | 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/ManagedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManagedObject.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public typealias ManagedObject = NSManagedObject 15 | 16 | // MARK:- 17 | 18 | extension ManagedObject { 19 | 20 | public final func inContext(_ otherContext: ManagedObjectContext) throws -> Self { 21 | if self.managedObjectContext === otherContext { 22 | return self 23 | } 24 | 25 | if self.objectID.isTemporaryID { 26 | try otherContext.obtainPermanentIDs(for: [self]) 27 | } 28 | 29 | let otherManagedObject = try otherContext.existingObject(with: self.objectID) 30 | 31 | return unsafeDowncast(otherManagedObject, to: type(of: self)) 32 | } 33 | 34 | } 35 | 36 | // MARK:- 37 | 38 | extension ManagedObject { 39 | 40 | public final func delete() { 41 | precondition(self.managedObjectContext != nil) 42 | self.managedObjectContext!.delete(self) 43 | } 44 | 45 | public final func refresh(mergeChanges: Bool = true) { 46 | precondition(self.managedObjectContext != nil) 47 | self.managedObjectContext!.refresh(self, mergeChanges: mergeChanges) 48 | } 49 | 50 | } 51 | 52 | // MARK: - 53 | 54 | extension ManagedObject { 55 | 56 | public subscript(key: String) -> Any? { 57 | return self.value(forKey: key) 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/ManagedObjectContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManagedObjectContext.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public typealias ManagedObjectContext = NSManagedObjectContext 13 | 14 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/PersistentContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistentContainer.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(ALCPersistentContainer) 13 | open class PersistentContainer: BasePersistentContainer { 14 | 15 | // MARK: - 16 | 17 | public private(set) lazy var backgroundContext: ManagedObjectContext = self.newBackgroundContext() 18 | 19 | // MARK: - 20 | 21 | fileprivate var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? 22 | 23 | 24 | // MARK: - 25 | 26 | /// Use caution when using this initializer. 27 | public convenience init(name: String? = nil) { 28 | try! self.init(name: name, managedObjectModel: type(of: self).managedObjectModel(), storageType: .disk, persistentStoreURL: try! type(of: self).persistentStoreURL(), persistentStoreDescriptionOptions: nil, ubiquitousConfiguration: nil) 29 | } 30 | 31 | public init(name: String? = nil, managedObjectModel: NSManagedObjectModel, storageType: PersistentContainerStorageType, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { 32 | // 33 | let name = name ?? persistentStoreURL.deletingPathExtension().lastPathComponent 34 | 35 | // 36 | if storageType == .disk { 37 | let directoryPath = persistentStoreURL.deletingLastPathComponent().path 38 | 39 | if !FileManager.default.fileExists(atPath: directoryPath) { 40 | try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true) 41 | } 42 | } 43 | 44 | // 45 | let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) 46 | 47 | // 48 | persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) 49 | persistentStoreDescription.shouldInferMappingModelAutomatically = true 50 | persistentStoreDescription.shouldMigrateStoreAutomatically = true 51 | 52 | // a chance for configuring options (such `NSPersistentHistoryTrackingKey`, for example) 53 | persistentStoreDescriptionOptions?.forEach { 54 | persistentStoreDescription.setOption($0.value, forKey: $0.key) 55 | } 56 | 57 | // deprecated ubiquitous support 58 | #if os(macOS) || os(iOS) 59 | if let ubiquitousConfiguration = ubiquitousConfiguration { 60 | persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) 61 | persistentStoreDescription.setOption(ubiquitousConfiguration.contentRelativePath as NSString, forKey: NSPersistentStoreUbiquitousContentURLKey) 62 | persistentStoreDescription.setOption(ubiquitousConfiguration.contentName as NSString, forKey: NSPersistentStoreUbiquitousContentNameKey) 63 | } 64 | #endif 65 | 66 | // 67 | try super.init(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription) { persistentContainer, _, error in 68 | guard error == nil else { 69 | return 70 | } 71 | 72 | // 73 | #if os(macOS) || os(iOS) 74 | if let _ = ubiquitousConfiguration { 75 | (persistentContainer as? PersistentContainer)?.didImportUbiquitousContentNotificationObserver = NotificationCenter.default.addObserver(forName: .NSPersistentStoreDidImportUbiquitousContentChanges, object: persistentContainer.persistentStoreCoordinator, queue: nil) { [weak persistentContainer] notification in 76 | guard let context = persistentContainer?.viewContext.parent ?? persistentContainer?.viewContext else { 77 | return 78 | } 79 | 80 | context.performAndWait { 81 | context.mergeChanges(fromContextDidSave: notification) 82 | } 83 | } 84 | } 85 | #endif 86 | } 87 | } 88 | 89 | public override init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { 90 | try super.init(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription, completionHandler: completionHandler) 91 | } 92 | 93 | deinit { 94 | if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { 95 | NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) 96 | self.didImportUbiquitousContentNotificationObserver = nil 97 | } 98 | } 99 | 100 | } 101 | 102 | 103 | // MARK: - 104 | 105 | open class BasePersistentContainer: NSPersistentContainer { 106 | 107 | // MARK: - 108 | 109 | public init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { 110 | // 111 | super.init(name: name, managedObjectModel: managedObjectModel) 112 | 113 | // we need to load synchronously in this implementation 114 | persistentStoreDescription.shouldAddStoreAsynchronously = false 115 | self.persistentStoreDescriptions = [persistentStoreDescription] 116 | 117 | // 118 | var outError: Swift.Error? 119 | 120 | self.loadPersistentStores { description, error in 121 | // 122 | if let error = error { 123 | outError = error 124 | } 125 | else { 126 | self.configureManagedObjectContext(self.viewContext) 127 | } 128 | 129 | // 130 | completionHandler(self, description, error) 131 | } 132 | 133 | if let outError = outError { 134 | throw outError 135 | } 136 | } 137 | 138 | // MARK: - 139 | 140 | open override func newBackgroundContext() -> NSManagedObjectContext { 141 | let context = super.newBackgroundContext() 142 | self.configureManagedObjectContext(context) 143 | 144 | return context 145 | } 146 | 147 | open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { 148 | super.performBackgroundTask { context in 149 | self.configureManagedObjectContext(context) 150 | block(context) 151 | } 152 | } 153 | 154 | // MARK: - 155 | 156 | open func configureManagedObjectContext(_ context: NSManagedObjectContext) { 157 | context.automaticallyMergesChangesFromParent = true 158 | context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 159 | } 160 | 161 | } 162 | 163 | 164 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistentContainerAuxiliarTypes.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 20/05/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum PersistentContainerStorageType { 12 | case disk 13 | case memory 14 | } 15 | 16 | public struct PersistentContainerUbiquitousConfiguration { 17 | public let containerIdentifier: String 18 | public let contentRelativePath: String 19 | public let contentName: String 20 | 21 | public init(containerIdentifier: String, contentRelativePath: String = "Data/TransactionLogs", contentName: String = "UbiquityStore") { 22 | self.containerIdentifier = containerIdentifier 23 | self.contentRelativePath = contentRelativePath 24 | self.contentName = contentName 25 | } 26 | 27 | } 28 | 29 | public enum PersistentContainerError: Error { 30 | case invalidManagedObjectModelURL 31 | case invalidPersistentStoreURL 32 | case invalidGroupContainerURL 33 | case applicationSupportDirectoryNotFound 34 | case managedObjectModelNotFound 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Sources/Core/Persistent Container/PersistentContainerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistentContainerType.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 20/05/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public protocol PersistentContainerType: AnyObject { 15 | associatedtype ManagedObjectContextType: ManagedObjectContext 16 | 17 | var viewContext: ManagedObjectContextType { get } 18 | var backgroundContext: ManagedObjectContextType { get } 19 | } 20 | 21 | extension PersistentContainer: PersistentContainerType {} 22 | extension CustomPersistentContainer: PersistentContainerType {} 23 | 24 | // MARK: - helper static methods 25 | 26 | extension PersistentContainerType { 27 | 28 | public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { 29 | let bundle = bundle ?? Bundle(for: Self.self) 30 | let name = name ?? bundle.bundleURL.deletingPathExtension().lastPathComponent 31 | 32 | let managedObjectModelURL = try self.managedObjectModelURL(withName: name, in: bundle) 33 | 34 | guard let managedObjectModel = NSManagedObjectModel(contentsOf: managedObjectModelURL) else { 35 | throw PersistentContainerError.managedObjectModelNotFound 36 | } 37 | 38 | return managedObjectModel 39 | } 40 | 41 | private static func managedObjectModelURL(withName name: String, in bundle: Bundle) throws -> URL { 42 | let resourceURL = bundle.url(forResource: name, withExtension: "momd") ?? bundle.url(forResource: name, withExtension: "mom") 43 | 44 | guard let managedObjectModelURL = resourceURL else { 45 | throw PersistentContainerError.invalidManagedObjectModelURL 46 | } 47 | 48 | return managedObjectModelURL 49 | } 50 | 51 | } 52 | 53 | extension PersistentContainerType { 54 | 55 | public static func persistentStoreURL(withName name: String? = nil, inPath path: String? = nil) throws -> URL { 56 | guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last else { 57 | throw PersistentContainerError.applicationSupportDirectoryNotFound 58 | } 59 | 60 | let name = name ?? Bundle.main.bundleURL.deletingPathExtension().lastPathComponent 61 | let path = path ?? name 62 | 63 | let persistentStoreURL = applicationSupportURL 64 | .appendingPathComponent(path, isDirectory: true) 65 | .appendingPathComponent("CoreData", isDirectory: true) 66 | .appendingPathComponent(name, isDirectory: false) 67 | .appendingPathExtension("sqlite") 68 | 69 | return persistentStoreURL 70 | } 71 | 72 | public static func persistentStoreURL(withName name: String, inPath path: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String) throws -> URL { 73 | guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) else { 74 | throw PersistentContainerError.invalidGroupContainerURL 75 | } 76 | 77 | let path = path ?? name 78 | 79 | let persistentStoreURL = containerURL 80 | .appendingPathComponent("Library", isDirectory: true) 81 | .appendingPathComponent("Application Support", isDirectory: true) 82 | .appendingPathComponent(path, isDirectory: true) 83 | .appendingPathComponent("CoreData", isDirectory: true) 84 | .appendingPathComponent(name, isDirectory: false) 85 | .appendingPathExtension("sqlite") 86 | 87 | return persistentStoreURL 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /Sources/Core/Query/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Config { 12 | public static var defaultBatchSize: Int = 20 13 | public static var defaultComparisonOptions: NSComparisonPredicate.Options = [.caseInsensitive, .diacriticInsensitive] 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Sources/Core/Query/Expression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Expression.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias Expression = NSExpression 12 | 13 | -------------------------------------------------------------------------------- /Sources/Core/Query/FetchRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchRequest.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public struct FetchRequest: Queryable { 15 | 16 | public typealias Element = Entity 17 | 18 | public fileprivate(set) var offset: Int = 0 19 | public fileprivate(set) var limit: Int = 0 20 | public fileprivate(set) var batchSize: Int = Config.defaultBatchSize 21 | 22 | public fileprivate(set) var predicate: Predicate? = nil 23 | public fileprivate(set) var sortDescriptors: [SortDescriptor]? = nil 24 | 25 | public init() { 26 | } 27 | 28 | public func toRaw() -> NSFetchRequest { 29 | let entityDescription = Entity.entity() 30 | let rawValue = NSFetchRequest(entityName: entityDescription.name!) 31 | 32 | rawValue.entity = entityDescription 33 | 34 | rawValue.fetchOffset = self.offset 35 | rawValue.fetchLimit = self.limit 36 | rawValue.fetchBatchSize = (self.limit > 0 && self.batchSize > self.limit ? 0 : self.batchSize) 37 | 38 | rawValue.predicate = self.predicate?.rawValue 39 | rawValue.sortDescriptors = self.sortDescriptors?.map { $0.rawValue } 40 | 41 | return rawValue 42 | } 43 | 44 | internal func reversed() -> FetchRequest { 45 | guard let existingSortDescriptors = self.sortDescriptors, !existingSortDescriptors.isEmpty else { 46 | return self 47 | } 48 | 49 | var clone = self 50 | clone.sortDescriptors = existingSortDescriptors.map { SortDescriptor(key: $0.key, ascending: !$0.ascending) } 51 | 52 | return clone 53 | } 54 | 55 | } 56 | 57 | // MARK: - 58 | 59 | extension FetchRequest { 60 | 61 | public func dropFirst(_ n: Int) -> FetchRequest { 62 | var clone = self 63 | clone.offset = n 64 | 65 | return clone 66 | } 67 | 68 | public func prefix(_ maxLength: Int) -> FetchRequest { 69 | var clone = self 70 | clone.limit = maxLength 71 | 72 | return clone 73 | } 74 | 75 | public func batchSize(_ batchSize: Int) -> FetchRequest { 76 | var clone = self 77 | clone.batchSize = batchSize 78 | 79 | return clone 80 | } 81 | 82 | } 83 | 84 | // MARK: - 85 | 86 | extension FetchRequest { 87 | 88 | public func filtered(using predicate: Predicate) -> FetchRequest { 89 | var clone = self 90 | 91 | if let existingPredicate = clone.predicate { 92 | clone.predicate = CompoundPredicate(andPredicateWithSubpredicates: [existingPredicate, predicate]) 93 | } 94 | else { 95 | clone.predicate = predicate 96 | } 97 | 98 | return clone 99 | } 100 | 101 | } 102 | 103 | // MARK: - 104 | 105 | extension FetchRequest { 106 | 107 | public func sorted(by sortDescriptor: SortDescriptor) -> FetchRequest { 108 | var clone = self 109 | 110 | if clone.sortDescriptors != nil { 111 | clone.sortDescriptors!.append(sortDescriptor) 112 | } 113 | else { 114 | clone.sortDescriptors = [sortDescriptor] 115 | } 116 | 117 | return clone 118 | } 119 | 120 | 121 | public func sorted(by sortDescriptors: [SortDescriptor]) -> FetchRequest { 122 | var clone = self 123 | 124 | if clone.sortDescriptors != nil { 125 | clone.sortDescriptors! += sortDescriptors 126 | } 127 | else { 128 | clone.sortDescriptors = sortDescriptors 129 | } 130 | 131 | return clone 132 | } 133 | 134 | public func sorted(by sortDescriptors: SortDescriptor...) -> FetchRequest { 135 | var clone = self 136 | 137 | if clone.sortDescriptors != nil { 138 | clone.sortDescriptors! += sortDescriptors 139 | } 140 | else { 141 | clone.sortDescriptors = sortDescriptors 142 | } 143 | 144 | return clone 145 | } 146 | 147 | } 148 | 149 | 150 | -------------------------------------------------------------------------------- /Sources/Core/Query/KeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPath.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - 12 | 13 | // .equalTo 14 | 15 | public func ==(left: KeyPath, right: Value) -> ComparisonPredicate { 16 | return ComparisonPredicate( 17 | leftExpression: Expression(forKeyPath: left), 18 | rightExpression: Expression(forConstantValue: right), 19 | modifier: .direct, 20 | type: .equalTo, 21 | options: right.comparisonOptions 22 | ) 23 | } 24 | 25 | public func ==(left: KeyPath>, right: Optional) -> ComparisonPredicate { 26 | return ComparisonPredicate( 27 | leftExpression: Expression(forKeyPath: left), 28 | rightExpression: Expression(forConstantValue: right), 29 | modifier: .direct, 30 | type: .equalTo, 31 | options: right?.comparisonOptions ?? [] 32 | ) 33 | } 34 | 35 | // .notEqualTo 36 | 37 | public func !=(left: KeyPath, right: Value) -> ComparisonPredicate { 38 | return ComparisonPredicate( 39 | leftExpression: Expression(forKeyPath: left), 40 | rightExpression: Expression(forConstantValue: right), 41 | modifier: .direct, 42 | type: .notEqualTo, 43 | options: right.comparisonOptions 44 | ) 45 | } 46 | 47 | public func !=(left: KeyPath>, right: Optional) -> ComparisonPredicate { 48 | return ComparisonPredicate( 49 | leftExpression: Expression(forKeyPath: left), 50 | rightExpression: Expression(forConstantValue: right), 51 | modifier: .direct, 52 | type: .notEqualTo, 53 | options: right?.comparisonOptions ?? [] 54 | ) 55 | } 56 | 57 | // .lessThan 58 | 59 | public func <(left: KeyPath, right: Value) -> ComparisonPredicate { 60 | return ComparisonPredicate( 61 | leftExpression: Expression(forKeyPath: left), 62 | rightExpression: Expression(forConstantValue: right), 63 | modifier: .direct, 64 | type: .lessThan, 65 | options: right.comparisonOptions 66 | ) 67 | } 68 | 69 | public func <(left: KeyPath>, right: Optional) -> ComparisonPredicate { 70 | return ComparisonPredicate( 71 | leftExpression: Expression(forKeyPath: left), 72 | rightExpression: Expression(forConstantValue: right), 73 | modifier: .direct, 74 | type: .lessThan, 75 | options: right?.comparisonOptions ?? [] 76 | ) 77 | } 78 | 79 | // .lessThanOrEqualTo 80 | 81 | public func <=(left: KeyPath, right: Value) -> ComparisonPredicate { 82 | return ComparisonPredicate( 83 | leftExpression: Expression(forKeyPath: left), 84 | rightExpression: Expression(forConstantValue: right), 85 | modifier: .direct, 86 | type: .lessThanOrEqualTo, 87 | options: right.comparisonOptions 88 | ) 89 | } 90 | 91 | public func <=(left: KeyPath>, right: Optional) -> ComparisonPredicate { 92 | return ComparisonPredicate( 93 | leftExpression: Expression(forKeyPath: left), 94 | rightExpression: Expression(forConstantValue: right), 95 | modifier: .direct, 96 | type: .lessThanOrEqualTo, 97 | options: right?.comparisonOptions ?? [] 98 | ) 99 | } 100 | 101 | // .greaterThan 102 | 103 | public func >(left: KeyPath, right: Value) -> ComparisonPredicate { 104 | return ComparisonPredicate( 105 | leftExpression: Expression(forKeyPath: left), 106 | rightExpression: Expression(forConstantValue: right), 107 | modifier: .direct, 108 | type: .greaterThan, 109 | options: right.comparisonOptions 110 | ) 111 | } 112 | 113 | public func >(left: KeyPath>, right: Optional) -> ComparisonPredicate { 114 | return ComparisonPredicate( 115 | leftExpression: Expression(forKeyPath: left), 116 | rightExpression: Expression(forConstantValue: right), 117 | modifier: .direct, 118 | type: .greaterThan, 119 | options: right?.comparisonOptions ?? [] 120 | ) 121 | } 122 | 123 | // .greaterThanOrEqualTo 124 | 125 | public func >=(left: KeyPath, right: Value) -> ComparisonPredicate { 126 | return ComparisonPredicate( 127 | leftExpression: Expression(forKeyPath: left), 128 | rightExpression: Expression(forConstantValue: right), 129 | modifier: .direct, 130 | type: .greaterThanOrEqualTo, 131 | options: right.comparisonOptions 132 | ) 133 | } 134 | 135 | public func >=(left: KeyPath>, right: Optional) -> ComparisonPredicate { 136 | return ComparisonPredicate( 137 | leftExpression: Expression(forKeyPath: left), 138 | rightExpression: Expression(forConstantValue: right), 139 | modifier: .direct, 140 | type: .greaterThanOrEqualTo, 141 | options: right?.comparisonOptions ?? [] 142 | ) 143 | } 144 | 145 | // .matches 146 | 147 | // .like 148 | 149 | // .beginsWith 150 | 151 | // .endsWith 152 | 153 | // .`in` 154 | 155 | // .contains 156 | 157 | // .between 158 | 159 | // MARK: - 160 | 161 | extension KeyPath { 162 | 163 | internal var pathString: String { 164 | return self._kvcKeyPathString! 165 | } 166 | 167 | } 168 | 169 | // MARK: - 170 | 171 | extension Equatable { 172 | 173 | fileprivate var comparisonOptions: ComparisonPredicate.Options { 174 | if self is String || self is NSString { 175 | return Config.defaultComparisonOptions 176 | } 177 | 178 | return [] 179 | } 180 | 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /Sources/Core/Query/Predicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Predicate.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public class Predicate { 15 | 16 | public let rawValue: NSPredicate 17 | 18 | public var predicateFormat: String { 19 | return self.rawValue.predicateFormat 20 | } 21 | 22 | public convenience init(format predicateFormat: String, argumentArray arguments: [Any]?) { 23 | self.init(rawValue: NSPredicate(format: predicateFormat, argumentArray: arguments)) 24 | } 25 | 26 | 27 | public convenience init(format predicateFormat: String, arguments argList: CVaListPointer) { 28 | self.init(rawValue: NSPredicate(format: predicateFormat, arguments: argList)) 29 | } 30 | 31 | public convenience init(value: Bool) { 32 | self.init(rawValue: NSPredicate(value: value)) 33 | } 34 | 35 | public init(rawValue: NSPredicate) { 36 | self.rawValue = rawValue 37 | } 38 | 39 | } 40 | 41 | // MARK: - 42 | 43 | public final class ComparisonPredicate: Predicate { 44 | 45 | public typealias Modifier = NSComparisonPredicate.Modifier 46 | public typealias Operator = NSComparisonPredicate.Operator 47 | public typealias Options = NSComparisonPredicate.Options 48 | 49 | public let leftExpression: NSExpression 50 | public let rightExpression: NSExpression 51 | 52 | public let modifier: Modifier 53 | public let operatorType: Operator 54 | 55 | public let options: Options 56 | 57 | public init(leftExpression left: Expression, rightExpression right: Expression, modifier: Modifier, type operatorType: Operator, options: Options = []) { 58 | // 59 | self.leftExpression = left 60 | self.rightExpression = right 61 | self.modifier = modifier 62 | self.operatorType = operatorType 63 | self.options = options 64 | 65 | // 66 | let predicate = NSComparisonPredicate( 67 | leftExpression: self.leftExpression, 68 | rightExpression: self.rightExpression, 69 | modifier: self.modifier, 70 | type: self.operatorType, 71 | options: self.options 72 | ) 73 | 74 | super.init(rawValue: predicate) 75 | } 76 | 77 | } 78 | 79 | // MARK: - 80 | 81 | public final class CompoundPredicate: Predicate { 82 | 83 | public typealias LogicalType = NSCompoundPredicate.LogicalType 84 | 85 | public let type: LogicalType 86 | public let subpredicates: [Predicate] 87 | 88 | public init(type: LogicalType, subpredicates: [Predicate]) { 89 | // 90 | self.type = type 91 | self.subpredicates = subpredicates 92 | 93 | // 94 | let predicate = NSCompoundPredicate(type: self.type, subpredicates: self.subpredicates.map { $0.rawValue }) 95 | 96 | super.init(rawValue: predicate) 97 | } 98 | 99 | public convenience init(andPredicateWithSubpredicates subpredicates: [Predicate]) { 100 | self.init(type: .and, subpredicates: subpredicates) 101 | } 102 | 103 | public convenience init(orPredicateWithSubpredicates subpredicates: [Predicate]) { 104 | self.init(type: .or, subpredicates: subpredicates) 105 | } 106 | 107 | public convenience init(notPredicateWithSubpredicate predicate: Predicate) { 108 | self.init(type: .not, subpredicates: [predicate]) 109 | } 110 | 111 | } 112 | 113 | // MARK: - 114 | 115 | public func &&(left: Predicate, right: Predicate) -> Predicate { 116 | return CompoundPredicate(type: .and, subpredicates: [left, right]) 117 | } 118 | 119 | public func ||(left: Predicate, right: Predicate) -> Predicate { 120 | return CompoundPredicate(type: .or, subpredicates: [left, right]) 121 | } 122 | 123 | prefix public func !(left: Predicate) -> Predicate { 124 | return CompoundPredicate(type: .not, subpredicates: [left]) 125 | } 126 | 127 | -------------------------------------------------------------------------------- /Sources/Core/Query/Query.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Query.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public struct Query { 15 | 16 | public let context: ManagedObjectContext 17 | public fileprivate(set) var fetchRequest: FetchRequest 18 | 19 | public init(in context: ManagedObjectContext, fetchRequest: FetchRequest = FetchRequest()) { 20 | self.context = context 21 | self.fetchRequest = fetchRequest 22 | } 23 | 24 | } 25 | 26 | // MARK: - 27 | 28 | extension Query { 29 | 30 | // 31 | 32 | public func execute() -> [Entity] { 33 | return self.execute(fetchRequest: self.fetchRequest) 34 | } 35 | 36 | fileprivate func execute(fetchRequest: FetchRequest) -> [Entity] { 37 | return try! self.context.fetch(fetchRequest.toRaw()) 38 | } 39 | 40 | // 41 | 42 | public func count() -> Int { 43 | return self.count(fetchRequest: self.fetchRequest) 44 | } 45 | 46 | fileprivate func count(fetchRequest: FetchRequest) -> Int { 47 | return try! self.context.count(for: fetchRequest.toRaw()) 48 | } 49 | 50 | // 51 | 52 | public func any() -> Bool { 53 | return self.prefix(1).count() > 0 54 | } 55 | 56 | public func none() -> Bool { 57 | return !self.any() 58 | } 59 | 60 | } 61 | 62 | // MARK: - 63 | 64 | extension Query { 65 | 66 | public func first() -> Entity? { 67 | return self.execute(fetchRequest: self.fetchRequest.prefix(1)).first 68 | } 69 | 70 | public func last() -> Entity? { 71 | guard let sortDescriptors = self.fetchRequest.sortDescriptors, !sortDescriptors.isEmpty else { 72 | return self.execute().last // not memory efficient, but will do the job 73 | } 74 | 75 | return self.execute(fetchRequest: self.fetchRequest.reversed().prefix(1)).first 76 | } 77 | 78 | } 79 | 80 | extension Query { 81 | 82 | func filtered(using predicate: Predicate) -> Entity? { 83 | return self.filtered(using: predicate).first() 84 | } 85 | 86 | public func first(where rawValue: NSPredicate) -> Entity? { 87 | return self.filtered(using: Predicate(rawValue: rawValue)).first() 88 | } 89 | 90 | public func first(where closure: () -> Predicate) -> Entity? { 91 | return self.filtered(using: closure()).first() 92 | } 93 | 94 | } 95 | 96 | extension Query { 97 | 98 | public func firstOrEmptyNew(where predicate: Predicate) -> Entity { 99 | guard let existingEntity = self.filtered(using: predicate).first() else { 100 | return self.new() 101 | } 102 | 103 | return existingEntity 104 | } 105 | 106 | public func firstOrEmptyNew(where rawValue: NSPredicate) -> Entity { 107 | guard let existingEntity = self.filtered(using: Predicate(rawValue: rawValue)).first() else { 108 | return self.new() 109 | } 110 | 111 | return existingEntity 112 | } 113 | 114 | public func firstOrEmptyNew(where closure: () -> Predicate) -> Entity { 115 | guard let existingEntity = self.filtered(using: closure()).first() else { 116 | return self.new() 117 | } 118 | 119 | return existingEntity 120 | } 121 | 122 | } 123 | 124 | 125 | // MARK: - 126 | 127 | extension Query: Sequence { 128 | 129 | // MARK: - 130 | 131 | public func makeIterator() -> QueryIterator { 132 | return QueryIterator(self.execute()) 133 | } 134 | 135 | public struct QueryIterator: IteratorProtocol { 136 | private let entities: [Entity] 137 | private var index: Int 138 | 139 | fileprivate init(_ entities: [Entity]) { 140 | self.entities = entities 141 | self.index = 0 142 | } 143 | 144 | public mutating func next() -> Entity? { 145 | guard self.index >= 0 && self.index < self.entities.count else { 146 | return nil 147 | } 148 | 149 | defer { self.index += 1 } 150 | 151 | return self.entities[index] 152 | } 153 | } 154 | 155 | // MARK: - 156 | 157 | public func dropLast(_ n: Int) -> Query { 158 | fatalError("Not applicable or not available.") 159 | } 160 | 161 | public func drop(while predicate: (Entity) throws -> Bool) rethrows -> Query { 162 | fatalError("Not applicable or not available.") 163 | } 164 | 165 | public func prefix(while predicate: (Entity) throws -> Bool) rethrows -> Query { 166 | fatalError("Not applicable or not available.") 167 | } 168 | 169 | public func suffix(_ maxLength: Int) -> Query { 170 | fatalError("Not applicable or not available.") 171 | } 172 | 173 | public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (Entity) throws -> Bool) rethrows -> [Query] { 174 | fatalError("Not applicable or not available.") 175 | } 176 | 177 | } 178 | 179 | // MARK: - 180 | 181 | extension Query { 182 | 183 | public func new() -> Entity { 184 | return Entity(context: self.context) 185 | } 186 | 187 | @discardableResult 188 | public func insert(with entityPropertiesInitializationClosure: (Entity) -> Void) -> Entity { 189 | let entity = self.new() 190 | entityPropertiesInitializationClosure(entity) 191 | 192 | return entity 193 | } 194 | 195 | public func delete(_ entity: Entity) { 196 | self.context.delete(entity) 197 | } 198 | 199 | public func deleteAll() throws { 200 | let fr = self.fetchRequest.toRaw() as NSFetchRequest 201 | fr.resultType = .managedObjectIDResultType 202 | 203 | let objectIDs = try self.context.fetch(fr) 204 | 205 | for objectID in objectIDs { 206 | let object = try self.context.existingObject(with: objectID) 207 | self.context.delete(object) 208 | } 209 | } 210 | 211 | public func refresh(_ entity: Entity, mergeChanges: Bool = true) { 212 | self.context.refresh(entity, mergeChanges: mergeChanges) 213 | } 214 | 215 | public func refreshAll() { 216 | self.context.refreshAllObjects() 217 | } 218 | 219 | } 220 | 221 | // MARK: - 222 | 223 | extension Query { 224 | 225 | public func toFetchRequestController(sectionName sectionNameKeyPathClosure: @autoclosure () -> KeyPath, cacheName: String? = nil) -> FetchRequestController { 226 | let sectionNameKeyPath = sectionNameKeyPathClosure().pathString 227 | return FetchRequestController(query: self, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 228 | } 229 | 230 | public func toFetchRequestController(sectionNameKeyPath: String? = nil, cacheName: String? = nil) -> FetchRequestController { 231 | return FetchRequestController(query: self, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 232 | } 233 | 234 | } 235 | 236 | // MARK: - Queryable 237 | 238 | extension Query: Queryable { 239 | 240 | public func dropFirst(_ n: Int) -> Query { 241 | var clone = self 242 | clone.fetchRequest = clone.fetchRequest.dropFirst(n) 243 | 244 | return clone 245 | } 246 | 247 | public func prefix(_ maxLength: Int) -> Query { 248 | var clone = self 249 | clone.fetchRequest = clone.fetchRequest.prefix(maxLength) 250 | 251 | return clone 252 | } 253 | 254 | public func batchSize(_ batchSize: Int) -> Query { 255 | var clone = self 256 | clone.fetchRequest = clone.fetchRequest.batchSize(batchSize) 257 | 258 | return clone 259 | } 260 | 261 | public func filtered(using predicate: Predicate) -> Query { 262 | var clone = self 263 | clone.fetchRequest = clone.fetchRequest.filtered(using: predicate) 264 | 265 | return clone 266 | } 267 | 268 | 269 | public func sorted(by sortDescriptor: SortDescriptor) -> Query { 270 | var clone = self 271 | clone.fetchRequest = clone.fetchRequest.sorted(by: sortDescriptor) 272 | 273 | return clone 274 | } 275 | 276 | public func sorted(by sortDescriptors: [SortDescriptor]) -> Query { 277 | var clone = self 278 | clone.fetchRequest = clone.fetchRequest.sorted(by: sortDescriptors) 279 | 280 | return clone 281 | } 282 | 283 | public func sorted(by sortDescriptors: SortDescriptor...) -> Query { 284 | var clone = self 285 | clone.fetchRequest = clone.fetchRequest.sorted(by: sortDescriptors) 286 | 287 | return clone 288 | } 289 | 290 | } 291 | 292 | -------------------------------------------------------------------------------- /Sources/Core/Query/Queryable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Queryable.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public protocol Queryable { 15 | 16 | associatedtype Element: ManagedObject 17 | 18 | func dropFirst(_ n: Int) -> Self 19 | func prefix(_ maxLength: Int) -> Self 20 | func batchSize(_ batchSize: Int) -> Self 21 | 22 | func filtered(using predicate: Predicate) -> Self 23 | 24 | func sorted(by sortDescriptor: SortDescriptor) -> Self 25 | func sorted(by sortDescriptors: [SortDescriptor]) -> Self 26 | func sorted(by sortDescriptors: SortDescriptor...) -> Self 27 | 28 | } 29 | 30 | // MARK: - 31 | 32 | extension Queryable { 33 | 34 | public func filtered(using rawValue: NSPredicate) -> Self { 35 | return self.filtered(using: Predicate(rawValue: rawValue)) 36 | } 37 | 38 | public func `where`(_ closure: () -> Predicate) -> Self { 39 | return self.filtered(using: closure()) 40 | } 41 | 42 | } 43 | 44 | extension Queryable { 45 | 46 | public func sorted(by rawValue: NSSortDescriptor) -> Self { 47 | return self.sorted(by: SortDescriptor(rawValue: rawValue)) 48 | } 49 | 50 | public func sorted(by rawValues: [NSSortDescriptor]) -> Self { 51 | return self.sorted(by: rawValues.map { SortDescriptor(rawValue: $0) }) 52 | } 53 | 54 | public func sorted(by rawValues: NSSortDescriptor...) -> Self { 55 | return self.sorted(by: rawValues.map { SortDescriptor(rawValue: $0) }) 56 | } 57 | 58 | } 59 | 60 | extension Queryable { 61 | 62 | // so we can write `sort(by: \.name)` instead of `sort(by: \Customer.name)` 63 | 64 | public func sorted(by closure: @autoclosure () -> KeyPath) -> Self { 65 | let sortDescriptor: SortDescriptor = .ascending(closure()) 66 | return self.sorted(by: sortDescriptor) 67 | } 68 | 69 | // aliases 70 | 71 | public func orderBy(_ closure: () -> SortDescriptor) -> Self { 72 | return self.sorted(by: closure()) 73 | } 74 | 75 | public func orderBy(_ closure: () -> KeyPath) -> Self { 76 | return self.sorted(by: closure()) 77 | } 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /Sources/Core/Query/SortDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortDescriptor.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public final class SortDescriptor { 13 | 14 | // MARK: - 15 | 16 | public static func ascending(_ key: String) -> SortDescriptor { 17 | return SortDescriptor(key: key, ascending: true) 18 | } 19 | 20 | public static func ascending(_ keyPath: KeyPath) -> SortDescriptor { 21 | return SortDescriptor(keyPath: keyPath, ascending: true) 22 | } 23 | 24 | public static func descending(_ key: String) -> SortDescriptor { 25 | return SortDescriptor(key: key, ascending: false) 26 | } 27 | 28 | public static func descending(_ keyPath: KeyPath) -> SortDescriptor { 29 | return SortDescriptor(keyPath: keyPath, ascending: false) 30 | } 31 | 32 | // MARK: - 33 | 34 | public let rawValue: NSSortDescriptor 35 | 36 | public let key: String 37 | public let ascending: Bool 38 | 39 | public convenience init(key: String, ascending: Bool) { 40 | self.init(rawValue: NSSortDescriptor(key: key, ascending: ascending)) 41 | } 42 | 43 | public convenience init(keyPath: KeyPath, ascending: Bool) { 44 | self.init(rawValue: NSSortDescriptor(keyPath: keyPath, ascending: ascending)) 45 | } 46 | 47 | public init(rawValue: NSSortDescriptor) { 48 | if let key = rawValue.key, let attribute = Entity.entity().attributesByName.first(where: { $0.key == key }), attribute.value.attributeType == .stringAttributeType { 49 | let comparisonOptions = Config.defaultComparisonOptions 50 | let selector: Selector? 51 | 52 | // recreate sort descriptor using comparison options 53 | if comparisonOptions.contains(.caseInsensitive) && comparisonOptions.contains(.diacriticInsensitive) { 54 | selector = #selector(NSString.localizedCaseInsensitiveCompare(_:)) 55 | } 56 | else if comparisonOptions.contains(.caseInsensitive) { 57 | selector = #selector(NSString.caseInsensitiveCompare(_:)) 58 | } 59 | else if comparisonOptions.contains(.diacriticInsensitive) { 60 | selector = #selector(NSString.localizedCompare(_:)) 61 | } 62 | else { 63 | selector = nil 64 | } 65 | 66 | self.rawValue = NSSortDescriptor(key: rawValue.key, ascending: rawValue.ascending, selector: selector) 67 | } 68 | else { 69 | self.rawValue = rawValue 70 | } 71 | 72 | // 73 | self.key = self.rawValue.key! 74 | self.ascending = self.rawValue.ascending 75 | } 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Sources/Fetch Request Controller/FetchRequestController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchRequestController.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 11/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // 13 | // we cannot inherit from `NSFetchedResultsController` here because the error: 14 | // "inheritance from a generic Objective-C class 'NSFetchedResultsController' must bind type parameters of 15 | // 'NSFetchedResultsController' to specific concrete types" 16 | // and 17 | // `FetchRequestController: NSFetchedResultsController` will not work for us 18 | // (curiously the "rawValue" inside our class is accepted to be `NSFetchedResultsController`) 19 | // 20 | 21 | // MARK: - 22 | 23 | public final class FetchRequestController { 24 | 25 | // MARK: - 26 | 27 | public let fetchRequest: FetchRequest 28 | public let rawValue: NSFetchedResultsController 29 | 30 | internal let rawValueDelegate: FetchedResultsControllerDelegate 31 | 32 | fileprivate let initialPredicate: Predicate? 33 | fileprivate let initialSortDescriptors: [SortDescriptor]? 34 | 35 | private var didPerformFetch = false 36 | 37 | // MARK: - 38 | 39 | public convenience init(query: Query, sectionName sectionNameKeyPathClosure: @autoclosure () -> KeyPath, cacheName: String? = nil) { 40 | let sectionNameKeyPath = sectionNameKeyPathClosure().pathString 41 | self.init(fetchRequest: query.fetchRequest, context: query.context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 42 | } 43 | 44 | public convenience init(query: Query, sectionNameKeyPath: String? = nil, cacheName: String? = nil) { 45 | self.init(fetchRequest: query.fetchRequest, context: query.context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 46 | } 47 | 48 | public convenience init(fetchRequest: FetchRequest, context: ManagedObjectContext, sectionName sectionNameKeyPathClosure: @autoclosure () -> KeyPath, cacheName: String? = nil) { 49 | let sectionNameKeyPath = sectionNameKeyPathClosure().pathString 50 | self.init(fetchRequest: fetchRequest, context: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 51 | } 52 | 53 | public init(fetchRequest: FetchRequest, context: ManagedObjectContext, sectionNameKeyPath: String? = nil, cacheName: String? = nil) { 54 | self.fetchRequest = fetchRequest 55 | self.rawValue = NSFetchedResultsController(fetchRequest: fetchRequest.toRaw() as NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) 56 | 57 | self.rawValueDelegate = FetchedResultsControllerDelegate() 58 | 59 | self.initialPredicate = fetchRequest.predicate 60 | self.initialSortDescriptors = fetchRequest.sortDescriptors 61 | 62 | // 63 | self.rawValue.delegate = self.rawValueDelegate 64 | } 65 | 66 | // MARK: - 67 | 68 | public func performFetch() { 69 | try! self.rawValue.performFetch() 70 | self.didPerformFetch = true 71 | } 72 | 73 | public func performFetchIfNeeded() { 74 | if !self.didPerformFetch { 75 | try! self.rawValue.performFetch() 76 | self.didPerformFetch = true 77 | } 78 | } 79 | 80 | } 81 | 82 | // MARK: - 83 | 84 | extension FetchRequestController { 85 | 86 | public var fetchedObjects: [Entity]? { 87 | self.performFetchIfNeeded() 88 | return self.rawValue.fetchedObjects 89 | } 90 | 91 | public func object(at indexPath: IndexPath) -> Entity { 92 | self.performFetchIfNeeded() 93 | return self.rawValue.object(at: indexPath) 94 | } 95 | 96 | public func indexPath(for object: Entity) -> IndexPath? { 97 | self.performFetchIfNeeded() 98 | return self.rawValue.indexPath(forObject: object) 99 | } 100 | 101 | } 102 | 103 | extension FetchRequestController { 104 | 105 | public func numberOfSections() -> Int { 106 | self.performFetchIfNeeded() 107 | return self.sections.count 108 | } 109 | 110 | public func numberOfObjects(inSection section: Int) -> Int { 111 | self.performFetchIfNeeded() 112 | return self.sections[section].numberOfObjects 113 | } 114 | 115 | } 116 | 117 | extension FetchRequestController { 118 | 119 | public var sections: [FetchedResultsSectionInfo] { 120 | self.performFetchIfNeeded() 121 | 122 | guard let result = self.rawValue.sections?.map({ FetchedResultsSectionInfo(rawValue: $0) }) else { 123 | fatalError("performFetch: hasn't been called.") 124 | } 125 | 126 | return result 127 | } 128 | 129 | public func section(forSectionIndexTitle title: String, at sectionIndex: Int) -> Int { 130 | self.performFetchIfNeeded() 131 | 132 | return self.rawValue.section(forSectionIndexTitle: title, at: sectionIndex) 133 | } 134 | 135 | } 136 | 137 | extension FetchRequestController { 138 | 139 | public func sectionIndexTitle(forSectionName sectionName: String) -> String? { 140 | self.performFetchIfNeeded() 141 | return self.rawValue.sectionIndexTitle(forSectionName: sectionName) 142 | } 143 | 144 | public var sectionIndexTitles: [String] { 145 | self.performFetchIfNeeded() 146 | return self.rawValue.sectionIndexTitles 147 | } 148 | 149 | } 150 | 151 | // MARK: - 152 | 153 | extension FetchRequestController { 154 | 155 | public func refresh(using predicate: Predicate?, keepOriginalPredicate: Bool) { 156 | self.assignPredicate(predicate, keepOriginalPredicate: keepOriginalPredicate) 157 | self.refresh() 158 | } 159 | 160 | public func refresh(using rawValue: NSPredicate, keepOriginalPredicate: Bool) { 161 | self.assignPredicate(Predicate(rawValue: rawValue), keepOriginalPredicate: keepOriginalPredicate) 162 | self.refresh() 163 | } 164 | 165 | public func refresh(using sortDescriptors: [SortDescriptor]?, keepOriginalSortDescriptors: Bool) { 166 | self.assignSortDescriptors(sortDescriptors, keepOriginalSortDescriptors: keepOriginalSortDescriptors) 167 | self.refresh() 168 | } 169 | 170 | public func refresh(using rawValues: [NSSortDescriptor], keepOriginalSortDescriptors: Bool) { 171 | self.assignSortDescriptors(rawValues.map({ SortDescriptor(rawValue: $0) }), keepOriginalSortDescriptors: keepOriginalSortDescriptors) 172 | self.refresh() 173 | } 174 | 175 | public func refresh(using predicate: Predicate?, sortDescriptors: [SortDescriptor]?, keepOriginalPredicate: Bool, keepOriginalSortDescriptors: Bool) { 176 | self.assignPredicate(predicate, keepOriginalPredicate: keepOriginalPredicate) 177 | self.assignSortDescriptors(sortDescriptors, keepOriginalSortDescriptors: keepOriginalSortDescriptors) 178 | 179 | self.refresh() 180 | } 181 | 182 | public func refresh(using predicateRawValue: NSPredicate, sortDescriptors sortDescriptorRawValues: [NSSortDescriptor], keepOriginalPredicate: Bool, keepOriginalSortDescriptors: Bool) { 183 | self.assignPredicate(Predicate(rawValue: predicateRawValue), keepOriginalPredicate: keepOriginalPredicate) 184 | self.assignSortDescriptors(sortDescriptorRawValues.map({ SortDescriptor(rawValue: $0) }), keepOriginalSortDescriptors: keepOriginalSortDescriptors) 185 | 186 | self.refresh() 187 | } 188 | 189 | // 190 | 191 | public func resetPredicate() { 192 | self.refresh(using: self.initialPredicate, keepOriginalPredicate: false) 193 | } 194 | 195 | public func resetSortDescriptors() { 196 | self.refresh(using: self.initialSortDescriptors, keepOriginalSortDescriptors: false) 197 | } 198 | 199 | public func resetPredicateAndSortDescriptors() { 200 | self.refresh(using: self.initialPredicate, sortDescriptors: self.initialSortDescriptors, keepOriginalPredicate: false, keepOriginalSortDescriptors: false) 201 | } 202 | 203 | } 204 | 205 | extension FetchRequestController { 206 | 207 | public func filter(using predicate: Predicate) { 208 | self.refresh(using: predicate, keepOriginalPredicate: true) 209 | } 210 | 211 | public func filter(using predicateClosure: () -> Predicate) { 212 | self.refresh(using: predicateClosure(), keepOriginalPredicate: true) 213 | } 214 | 215 | public func filter(using rawValue: NSPredicate) { 216 | self.refresh(using: Predicate(rawValue: rawValue), keepOriginalPredicate: true) 217 | } 218 | 219 | 220 | public func resetFilter() { 221 | self.resetPredicate() 222 | } 223 | 224 | public func reset() { 225 | self.resetPredicateAndSortDescriptors() 226 | } 227 | 228 | } 229 | 230 | extension FetchRequestController { 231 | 232 | fileprivate func assignPredicate(_ predicate: Predicate?, keepOriginalPredicate: Bool) { 233 | let newPredicate: Predicate? 234 | 235 | if keepOriginalPredicate { 236 | if let initialPredicate = self.initialPredicate { 237 | if let predicate = predicate { 238 | newPredicate = CompoundPredicate(type: .and, subpredicates: [initialPredicate, predicate]) 239 | } 240 | else { 241 | newPredicate = initialPredicate 242 | } 243 | } 244 | else { 245 | newPredicate = predicate 246 | } 247 | } 248 | else { 249 | newPredicate = predicate 250 | } 251 | 252 | self.rawValue.fetchRequest.predicate = newPredicate?.rawValue 253 | } 254 | 255 | fileprivate func assignSortDescriptors(_ sortDescriptors: [SortDescriptor]?, keepOriginalSortDescriptors: Bool) { 256 | let newSortDescriptors: [SortDescriptor]? 257 | 258 | if keepOriginalSortDescriptors { 259 | if let initialSortDescriptors = self.initialSortDescriptors { 260 | if let sortDescriptors = sortDescriptors { 261 | var tempSortDescriptors = initialSortDescriptors 262 | tempSortDescriptors += sortDescriptors 263 | 264 | newSortDescriptors = tempSortDescriptors 265 | } 266 | else { 267 | newSortDescriptors = initialSortDescriptors 268 | } 269 | } 270 | else { 271 | newSortDescriptors = sortDescriptors 272 | } 273 | } 274 | else { 275 | newSortDescriptors = sortDescriptors 276 | } 277 | 278 | self.rawValue.fetchRequest.sortDescriptors = newSortDescriptors?.map { $0.rawValue } 279 | } 280 | 281 | } 282 | 283 | 284 | -------------------------------------------------------------------------------- /Sources/Fetch Request Controller/FetchedResultsControllerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchedResultsControllerDelegate.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 12/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - 13 | 14 | public final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { 15 | 16 | // MARK: - 17 | 18 | internal typealias NeedsReloadDataClosure = () -> Void 19 | public typealias ChangeContentClosure = () -> Void 20 | public typealias ChangeSectionClosure = (FetchedResultsSectionInfo, Int) -> Void 21 | public typealias ChangeItemClosure = (Entity, IndexPath) -> Void 22 | public typealias MoveItemClosure = (Entity, IndexPath, IndexPath) -> Void 23 | public typealias SectionIndexTitleClosure = (String) -> String? 24 | 25 | // MARK: - 26 | 27 | fileprivate var needsReloadDataClosure: NeedsReloadDataClosure? 28 | 29 | fileprivate lazy var willChangeContentClosuresContainer = ClosuresContainer() 30 | fileprivate lazy var didChangeContentClosuresContainer = ClosuresContainer() 31 | 32 | fileprivate lazy var didInsertSectionClosuresContainer = ClosuresContainer() 33 | fileprivate lazy var didDeleteSectionClosuresContainer = ClosuresContainer() 34 | 35 | fileprivate lazy var didInsertObjectClosuresContainer = ClosuresContainer() 36 | fileprivate lazy var didDeleteObjectClosuresContainer = ClosuresContainer() 37 | fileprivate lazy var didUpdateObjectClosuresContainer = ClosuresContainer() 38 | fileprivate lazy var didMoveObjectClosuresContainer = ClosuresContainer() 39 | 40 | fileprivate var sectionIndexTitleClosure: SectionIndexTitleClosure? 41 | 42 | // MARK: - 43 | 44 | public func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 45 | self.willChangeContentClosuresContainer.closures.forEach { $0() } 46 | } 47 | 48 | public func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { 49 | let sectionInfo = FetchedResultsSectionInfo(rawValue: sectionInfo) 50 | 51 | switch type { 52 | case .insert: 53 | self.didInsertSectionClosuresContainer.closures.forEach { $0(sectionInfo, sectionIndex) } 54 | 55 | case .delete: 56 | self.didDeleteSectionClosuresContainer.closures.forEach { $0(sectionInfo, sectionIndex) } 57 | 58 | default: 59 | break 60 | } 61 | } 62 | 63 | 64 | public func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 65 | switch type { 66 | case .insert: 67 | self.didInsertObjectClosuresContainer.closures.forEach { $0(anObject as! Entity, newIndexPath!) } 68 | 69 | case .delete: 70 | self.didDeleteObjectClosuresContainer.closures.forEach { $0(anObject as! Entity, indexPath!) } 71 | 72 | case .update: 73 | self.didUpdateObjectClosuresContainer.closures.forEach { $0(anObject as! Entity, indexPath!) } 74 | 75 | case .move: 76 | self.didMoveObjectClosuresContainer.closures.forEach { $0(anObject as! Entity, indexPath!, newIndexPath!) } 77 | 78 | @unknown default: 79 | // do nothing 80 | break 81 | } 82 | } 83 | 84 | 85 | public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 86 | self.didChangeContentClosuresContainer.closures.forEach { $0() } 87 | } 88 | 89 | 90 | public func controller(_ controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? { 91 | return self.sectionIndexTitleClosure?(sectionName) 92 | } 93 | 94 | 95 | } 96 | 97 | // MARK: - 98 | 99 | public final class ClosuresContainer { 100 | 101 | fileprivate lazy var closures = [Closure]() 102 | 103 | internal func removeAll() { 104 | self.closures.removeAll() 105 | } 106 | 107 | } 108 | 109 | public func +=(left: ClosuresContainer, right: Closure) { 110 | left.closures.append(right) 111 | } 112 | 113 | 114 | // MARK: - FetchRequestController extensions 115 | 116 | extension FetchRequestController { 117 | 118 | public func removeAllBindings() { 119 | self.rawValueDelegate.needsReloadDataClosure = nil 120 | 121 | self.rawValueDelegate.willChangeContentClosuresContainer.removeAll() 122 | self.rawValueDelegate.didChangeContentClosuresContainer.removeAll() 123 | 124 | self.rawValueDelegate.didInsertSectionClosuresContainer.removeAll() 125 | self.rawValueDelegate.didDeleteSectionClosuresContainer.removeAll() 126 | 127 | self.rawValueDelegate.didInsertObjectClosuresContainer.removeAll() 128 | self.rawValueDelegate.didDeleteObjectClosuresContainer.removeAll() 129 | self.rawValueDelegate.didUpdateObjectClosuresContainer.removeAll() 130 | self.rawValueDelegate.didMoveObjectClosuresContainer.removeAll() 131 | 132 | self.rawValueDelegate.sectionIndexTitleClosure = nil 133 | } 134 | 135 | } 136 | 137 | extension FetchRequestController { 138 | 139 | public func refresh() { 140 | self.rawValueDelegate.needsReloadDataClosure?() 141 | 142 | self.rawValueDelegate.willChangeContentClosuresContainer.closures.forEach { $0() } 143 | 144 | if let cacheName = self.rawValue.cacheName { 145 | NSFetchedResultsController.deleteCache(withName: cacheName) 146 | } 147 | 148 | self.performFetch() 149 | 150 | self.rawValueDelegate.didChangeContentClosuresContainer.closures.forEach { $0() } 151 | } 152 | 153 | } 154 | 155 | extension FetchRequestController { 156 | 157 | @discardableResult 158 | internal func needsReloadData(closure: @escaping FetchedResultsControllerDelegate.NeedsReloadDataClosure) -> Self { 159 | self.rawValueDelegate.needsReloadDataClosure = closure 160 | return self 161 | } 162 | 163 | } 164 | 165 | extension FetchRequestController { 166 | 167 | @discardableResult 168 | public func willChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { 169 | self.rawValueDelegate.willChangeContentClosuresContainer += closure 170 | return self 171 | } 172 | 173 | @discardableResult 174 | public func didChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { 175 | self.rawValueDelegate.didChangeContentClosuresContainer += closure 176 | return self 177 | } 178 | 179 | @discardableResult 180 | public func didInsertSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { 181 | self.rawValueDelegate.didInsertSectionClosuresContainer += closure 182 | return self 183 | } 184 | 185 | @discardableResult 186 | public func didDeleteSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { 187 | self.rawValueDelegate.didDeleteSectionClosuresContainer += closure 188 | return self 189 | } 190 | 191 | @discardableResult 192 | public func didInsertObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { 193 | self.rawValueDelegate.didInsertObjectClosuresContainer += closure 194 | return self 195 | } 196 | 197 | @discardableResult 198 | public func didDeleteObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { 199 | self.rawValueDelegate.didDeleteObjectClosuresContainer += closure 200 | return self 201 | } 202 | 203 | @discardableResult 204 | public func didUpdateObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { 205 | self.rawValueDelegate.didUpdateObjectClosuresContainer += closure 206 | return self 207 | } 208 | 209 | @discardableResult 210 | public func didMoveObject(closure: @escaping FetchedResultsControllerDelegate.MoveItemClosure) -> Self { 211 | self.rawValueDelegate.didMoveObjectClosuresContainer += closure 212 | return self 213 | } 214 | 215 | @discardableResult 216 | public func sectionIndexTitle(closure: @escaping FetchedResultsControllerDelegate.SectionIndexTitleClosure) -> Self { 217 | self.rawValueDelegate.sectionIndexTitleClosure = closure 218 | return self 219 | } 220 | 221 | } 222 | 223 | -------------------------------------------------------------------------------- /Sources/Fetch Request Controller/FetchedResultsSectionInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchedResultsSectionInfo.swift 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 12/03/18. 6 | // Copyright © 2018 Alecrim. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public final class FetchedResultsSectionInfo { 13 | 14 | private let rawValue: NSFetchedResultsSectionInfo 15 | 16 | /// The name of the section. 17 | public var name: String { return self.rawValue.name } 18 | 19 | /// The index title of the section. 20 | public var indexTitle: String? { return self.rawValue.indexTitle } 21 | 22 | /// The number of entities (rows) in the section. 23 | public var numberOfObjects: Int { return self.rawValue.numberOfObjects } 24 | 25 | /// The array of entities in the section. 26 | public var objects: [Entity] { 27 | guard let result = self.rawValue.objects as? [Entity] else { 28 | fatalError("performFetch: hasn't been called.") 29 | } 30 | 31 | return result 32 | } 33 | 34 | internal init(rawValue: NSFetchedResultsSectionInfo) { 35 | self.rawValue = rawValue 36 | } 37 | 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /Supporting Files/AlecrimCoreData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AlecrimCoreData.h 3 | // AlecrimCoreData 4 | // 5 | // Created by Vanderlei Martinelli on 2014-06-24. 6 | // Copyright © 2014-2019 Alecrim. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | FOUNDATION_EXPORT double AlecrimCoreDataVersionNumber; 12 | FOUNDATION_EXPORT const unsigned char AlecrimCoreDataVersionString[]; 13 | -------------------------------------------------------------------------------- /Supporting Files/AlecrimCoreData.xcconfig: -------------------------------------------------------------------------------- 1 | // Configuration settings file format documentation can be found at: 2 | // https://help.apple.com/xcode/#/dev745c5c974 3 | 4 | SUPPORTED_PLATFORMS = iphoneos macosx appletvos watchos iphonesimulator appletvsimulator watchsimulator 5 | 6 | SWIFT_VERSION = 5.0 7 | 8 | MACOSX_DEPLOYMENT_TARGET = 10.12 9 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 10 | WATCHOS_DEPLOYMENT_TARGET = 3.0 11 | TVOS_DEPLOYMENT_TARGET = 10.0 12 | -------------------------------------------------------------------------------- /Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 7.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2014-2019 Vanderlei Martinelli. All rights reserved. 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/AlecrimCoreDataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlecrimCoreDataTests.swift 3 | // AlecrimCoreDataTests 4 | // 5 | // Created by Vanderlei Martinelli on 12/04/19. 6 | // Copyright © 2019 Alecrim. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AlecrimCoreData 11 | 12 | class AlecrimCoreDataTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | --------------------------------------------------------------------------------