From 2071ce722eb248bb231e36d84032c9f6f171a7b8 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Mon, 1 Feb 2016 01:05:26 +0900 Subject: [PATCH] WIP: iCloud support --- CoreStore.xcodeproj/project.pbxproj | 18 ++ CoreStore/NSError+CoreStore.swift | 5 + CoreStore/iCloud/DataStack+iCloud.swift | 169 ++++++++++++++++++ .../CoreStoreDemo/Base.lproj/LaunchScreen.xib | 6 +- 4 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 CoreStore/iCloud/DataStack+iCloud.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index afc7d19..5536c4b 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -85,6 +85,10 @@ B519E45A1C4CD2DA00E7B469 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B519E4571C4CD2CA00E7B469 /* GCDKit.framework */; }; B519E45B1C4CD2ED00E7B469 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B519E4571C4CD2CA00E7B469 /* GCDKit.framework */; }; B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; + B51F259A1C5747DD0083A5DD /* DataStack+iCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */; }; + B51F259B1C57875E0083A5DD /* DataStack+iCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */; }; + B51F259C1C57875F0083A5DD /* DataStack+iCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */; }; + B51F259D1C5787600083A5DD /* DataStack+iCloud.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */; }; B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; B52DD17E1BE1F8CD00949AFE /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52DD1741BE1F8CC00949AFE /* CoreStore.framework */; }; @@ -306,6 +310,7 @@ B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; B519E4571C4CD2CA00E7B469 /* GCDKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GCDKit.framework; path = "../../Library/Developer/Xcode/DerivedData/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = ""; }; B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = ""; }; + B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+iCloud.swift"; sourceTree = ""; }; B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFetchedResultsController+Convenience.swift"; sourceTree = ""; }; B52DD1741BE1F8CC00949AFE /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -478,6 +483,7 @@ 2F291E2619C6D3CF007AF63F /* CoreStore.swift */, B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */, B5E84EDA1AFF84500064E85B /* Setting Up */, + B51F25981C5747790083A5DD /* iCloud */, B5E84EE21AFF84610064E85B /* Logging */, B5E84EE91AFF846E0064E85B /* Saving and Processing */, B5E834B61B7630BD001D3D50 /* Importing Data */, @@ -540,6 +546,14 @@ name = Frameworks; sourceTree = ""; }; + B51F25981C5747790083A5DD /* iCloud */ = { + isa = PBXGroup; + children = ( + B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */, + ); + path = iCloud; + sourceTree = ""; + }; B56964D11B22FF700075EE4A /* Migrating */ = { isa = PBXGroup; children = ( @@ -987,6 +1001,7 @@ B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, + B51F259A1C5747DD0083A5DD /* DataStack+iCloud.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */, @@ -1065,6 +1080,7 @@ 82BA18CD1C4BBD7100A0916E /* AssociatedObjects.swift in Sources */, 82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */, 82BA18A41C4BBD2200A0916E /* PersistentStoreResult.swift in Sources */, + B51F259B1C57875E0083A5DD /* DataStack+iCloud.swift in Sources */, 82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */, 82BA18A91C4BBD3100A0916E /* Into.swift in Sources */, 82BA18D11C4BBD7100A0916E /* NotificationObserver.swift in Sources */, @@ -1117,6 +1133,7 @@ B52DD1BF1BE1F94600949AFE /* AssociatedObjects.swift in Sources */, B52DD1A11BE1F92C00949AFE /* DataStack+Transaction.swift in Sources */, B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */, + B51F259D1C5787600083A5DD /* DataStack+iCloud.swift in Sources */, B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */, B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */, B52DD1A61BE1F92F00949AFE /* BaseDataTransaction+Importing.swift in Sources */, @@ -1208,6 +1225,7 @@ B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */, B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B56321881BD65216006C9394 /* BaseDataTransaction.swift in Sources */, + B51F259C1C57875F0083A5DD /* DataStack+iCloud.swift in Sources */, B56321A31BD65216006C9394 /* DataStack+Migration.swift in Sources */, B56321901BD65216006C9394 /* ImportableUniqueObject.swift in Sources */, B56321871BD65216006C9394 /* Into.swift in Sources */, diff --git a/CoreStore/NSError+CoreStore.swift b/CoreStore/NSError+CoreStore.swift index d32a995..f5168d8 100644 --- a/CoreStore/NSError+CoreStore.swift +++ b/CoreStore/NSError+CoreStore.swift @@ -58,6 +58,11 @@ public enum CoreStoreErrorCode: Int { An `NSMappingModel` could not be found for a specific source and destination model versions. */ case MappingModelNotFound + + /** + The container could not be located or if iCloud storage is unavailable for the current user or device. + */ + case ICloudContainerNotFound } diff --git a/CoreStore/iCloud/DataStack+iCloud.swift b/CoreStore/iCloud/DataStack+iCloud.swift new file mode 100644 index 0000000..fad2ebc --- /dev/null +++ b/CoreStore/iCloud/DataStack+iCloud.swift @@ -0,0 +1,169 @@ +// +// DataStack+iCloud.swift +// CoreStore +// +// Copyright © 2016 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData +#if USE_FRAMEWORKS + import GCDKit +#endif + +public extension DataStack { + + public func addICloudStore(ubiquitousContentName: String, ubiquitousContentURLRelativePath: String? = nil, ubiquitousContainerID: String? = nil, ubiquitousPeerToken: String? = nil, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), automigrating: Bool, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? { + + CoreStore.assert( + !ubiquitousContentName.isEmpty, + "The ubiquitousContentName cannot be empty." + ) + CoreStore.assert( + !ubiquitousContentName.containsString("."), + "The ubiquitousContentName cannot contain periods." + ) + CoreStore.assert( + ubiquitousContentURLRelativePath?.isEmpty != true, + "The ubiquitousContentURLRelativePath should not be empty if provided." + ) + CoreStore.assert( + ubiquitousPeerToken?.isEmpty != true, + "The ubiquitousPeerToken should not be empty if provided." + ) + + let fileManager = NSFileManager.defaultManager() + guard let fileURL = fileManager.URLForUbiquityContainerIdentifier(ubiquitousContainerID) else { + + throw NSError(coreStoreErrorCode: .ICloudContainerNotFound) + } + + let coordinator = self.coordinator; + if let store = coordinator.persistentStoreForURL(fileURL) { + + guard store.type == NSSQLiteStoreType + && store.configurationName == (configuration ?? Into.defaultConfigurationName) else { + + let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL) + CoreStore.handleError( + error, + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." + ) + throw error + } + + GCDQueue.Main.async { + + completion(PersistentStoreResult(store)) + } + return nil + } + + _ = try? fileManager.createDirectoryAtURL( + fileURL.URLByDeletingLastPathComponent!, + withIntermediateDirectories: true, + attributes: nil + ) + + var options = self.optionsForSQLiteStore() + options[NSPersistentStoreUbiquitousContentNameKey] = ubiquitousContentName + options[NSMigratePersistentStoresAutomaticallyOption] = automigrating + options[NSInferMappingModelAutomaticallyOption] = automigrating + + if let ubiquitousContentURLRelativePath = ubiquitousContentURLRelativePath { + + options[NSPersistentStoreUbiquitousContentURLKey] = ubiquitousContentURLRelativePath + } + if let ubiquitousContainerID = ubiquitousContainerID { + + options[NSPersistentStoreUbiquitousContainerIdentifierKey] = ubiquitousContainerID + } + if let ubiquitousPeerToken = ubiquitousPeerToken { + + options[NSPersistentStoreUbiquitousPeerTokenOption] = ubiquitousPeerToken + } + + var store: NSPersistentStore? + var storeError: NSError? + coordinator.performBlockAndWait { + + do { + + store = try coordinator.addPersistentStoreWithType( + NSSQLiteStoreType, + configuration: configuration, + URL: fileURL, + options: options + ) + } + catch { + + storeError = error as NSError + } + } + + if let store = store { + + self.updateMetadataForPersistentStore(store) + return store + } + + if let error = storeError + where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) { + + fileManager.removeSQLiteStoreAtURL(fileURL) + + var store: NSPersistentStore? + coordinator.performBlockAndWait { + + do { + + store = try coordinator.addPersistentStoreWithType( + NSSQLiteStoreType, + configuration: configuration, + URL: fileURL, + options: options + ) + } + catch { + + storeError = error as NSError + } + } + + if let store = store { + + self.updateMetadataForPersistentStore(store) + return store + } + } + + let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError) + CoreStore.handleError( + error, + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"." + ) + throw error + + + + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib b/CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib index c3e905c..2aec9f6 100644 --- a/CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib +++ b/CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib @@ -1,7 +1,7 @@ - + - + @@ -11,7 +11,7 @@ -