WIP: iCloud support

This commit is contained in:
John Rommel Estropia
2016-02-01 01:05:26 +09:00
parent df866718cf
commit 2071ce722e
4 changed files with 195 additions and 3 deletions

View File

@@ -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 = "<group>"; };
B519E4571C4CD2CA00E7B469 /* GCDKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GCDKit.framework; path = "../../Library/Developer/Xcode/DerivedData/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = "<group>"; };
B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = "<group>"; };
B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+iCloud.swift"; sourceTree = "<group>"; };
B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFetchedResultsController+Convenience.swift"; sourceTree = "<group>"; };
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 = "<group>";
};
B51F25981C5747790083A5DD /* iCloud */ = {
isa = PBXGroup;
children = (
B51F25991C5747DD0083A5DD /* DataStack+iCloud.swift */,
);
path = iCloud;
sourceTree = "<group>";
};
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 */,

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
@@ -11,7 +11,7 @@
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>