here we go

This commit is contained in:
John Rommel Estropia
2014-12-07 21:38:54 +09:00
parent 25bc4a189f
commit daa5e64ae0
18 changed files with 1490 additions and 12 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
HardcoreData.xcodeproj/xcuserdata

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "Libraries/JEToolkit"]
path = Libraries/JEToolkit
url = git@github.com:JohnEstropia/JEToolkit.git
[submodule "Libraries/GCDKit"]
path = Libraries/GCDKit
url = git@github.com:JohnEstropia/GCDKit.git

View File

@@ -9,8 +9,54 @@
/* Begin PBXBuildFile section */
2F03A53619C5C6DA005002A5 /* HardcoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F03A53519C5C6DA005002A5 /* HardcoreData.h */; settings = {ATTRIBUTES = (Public, ); }; };
2F03A54019C5C6DA005002A5 /* HardcoreDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */; };
2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; };
2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* HardcoreData.swift */; };
B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFD36D1A0775F000B7885F /* SaveResult.swift */; };
B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; };
B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */; };
B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */; };
B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */; };
B5D399F119FC818E000E91BB /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D399F019FC818E000E91BB /* DataStack.swift */; };
B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */; };
B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; };
B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0319FD00DE000E91BB /* UIKit.framework */; };
B5D8080E1A3471A500A44484 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808021A34715700A44484 /* GCDKit.framework */; };
B5D8080F1A3471A900A44484 /* JEToolkit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808081A34715700A44484 /* JEToolkit.framework */; };
B5E209E01A0726460089C9D4 /* NSManagedObject+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */; };
B5F539901A17A6FC00EC763B /* QueryDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5398F1A17A6FC00EC763B /* QueryDescriptor.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
B5D808011A34715700A44484 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B5D806C51A34715700A44484 /* GCDKit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2FBBCACB19A9FE610070E4AB;
remoteInfo = GCDKit;
};
B5D808031A34715700A44484 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B5D806C51A34715700A44484 /* GCDKit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2FBBCAD619A9FE610070E4AB;
remoteInfo = GCDKitTests;
};
B5D808071A34715700A44484 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B5D8074E1A34715700A44484 /* JEToolkit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2F74E6F519DFCC7A00FB0C88;
remoteInfo = JEToolkit;
};
B5D808091A34715700A44484 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B5D8074E1A34715700A44484 /* JEToolkit.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2F74E70019DFCC7A00FB0C88;
remoteInfo = JEToolkitTests;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F03A53019C5C6DA005002A5 /* HardcoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HardcoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2F03A53419C5C6DA005002A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -18,6 +64,22 @@
2F03A53B19C5C6DA005002A5 /* HardcoreDataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HardcoreDataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2F03A53E19C5C6DA005002A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcoreDataTests.swift; sourceTree = "<group>"; };
2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
2F291E2619C6D3CF007AF63F /* HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardcoreData.swift; sourceTree = "<group>"; };
B5CFD36D1A0775F000B7885F /* SaveResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveResult.swift; sourceTree = "<group>"; };
B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = "<group>"; };
B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTransaction.swift; sourceTree = "<group>"; };
B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStoreResult.swift; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+HardcoreData.swift"; sourceTree = "<group>"; };
B5D399F019FC818E000E91BB /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = "<group>"; };
B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+HardcoreData.swift"; sourceTree = "<group>"; };
B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
B5D39A0319FD00DE000E91BB /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
B5D806C51A34715700A44484 /* GCDKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = GCDKit.xcodeproj; sourceTree = "<group>"; };
B5D8074E1A34715700A44484 /* JEToolkit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = JEToolkit.xcodeproj; sourceTree = "<group>"; };
B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+HardcoreData.swift"; sourceTree = "<group>"; };
B5F3D98419F3EB8E009690A6 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
B5F5398F1A17A6FC00EC763B /* QueryDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryDescriptor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -25,6 +87,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B5D8080F1A3471A900A44484 /* JEToolkit.framework in Frameworks */,
B5D8080E1A3471A500A44484 /* GCDKit.framework in Frameworks */,
B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */,
B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */,
2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -41,6 +108,8 @@
2F03A52619C5C6DA005002A5 = {
isa = PBXGroup;
children = (
B5D806BB1A34715700A44484 /* Libraries */,
2F291E3119C6D4D3007AF63F /* Frameworks */,
2F03A53219C5C6DA005002A5 /* HardcoreData */,
2F03A53C19C5C6DA005002A5 /* HardcoreDataTests */,
2F03A53119C5C6DA005002A5 /* Products */,
@@ -59,7 +128,17 @@
2F03A53219C5C6DA005002A5 /* HardcoreData */ = {
isa = PBXGroup;
children = (
B5D399F019FC818E000E91BB /* DataStack.swift */,
B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */,
2F03A53519C5C6DA005002A5 /* HardcoreData.h */,
2F291E2619C6D3CF007AF63F /* HardcoreData.swift */,
B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */,
B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */,
B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */,
B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */,
B5F5398F1A17A6FC00EC763B /* QueryDescriptor.swift */,
B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */,
B5CFD36D1A0775F000B7885F /* SaveResult.swift */,
2F03A53319C5C6DA005002A5 /* Supporting Files */,
);
path = HardcoreData;
@@ -68,6 +147,7 @@
2F03A53319C5C6DA005002A5 /* Supporting Files */ = {
isa = PBXGroup;
children = (
B5F3D98419F3EB8E009690A6 /* LICENSE */,
2F03A53419C5C6DA005002A5 /* Info.plist */,
);
name = "Supporting Files";
@@ -90,6 +170,59 @@
name = "Supporting Files";
sourceTree = "<group>";
};
2F291E3119C6D4D3007AF63F /* Frameworks */ = {
isa = PBXGroup;
children = (
2F03A54C19C5C872005002A5 /* CoreData.framework */,
B5D39A0119FD00C9000E91BB /* Foundation.framework */,
B5D39A0319FD00DE000E91BB /* UIKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
B5D806BB1A34715700A44484 /* Libraries */ = {
isa = PBXGroup;
children = (
B5D806BC1A34715700A44484 /* GCDKit */,
B5D806CD1A34715700A44484 /* JEToolkit */,
);
path = Libraries;
sourceTree = "<group>";
};
B5D806BC1A34715700A44484 /* GCDKit */ = {
isa = PBXGroup;
children = (
B5D806C51A34715700A44484 /* GCDKit.xcodeproj */,
);
path = GCDKit;
sourceTree = "<group>";
};
B5D806C61A34715700A44484 /* Products */ = {
isa = PBXGroup;
children = (
B5D808021A34715700A44484 /* GCDKit.framework */,
B5D808041A34715700A44484 /* GCDKitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
B5D806CD1A34715700A44484 /* JEToolkit */ = {
isa = PBXGroup;
children = (
B5D8074E1A34715700A44484 /* JEToolkit.xcodeproj */,
);
path = JEToolkit;
sourceTree = "<group>";
};
B5D8074F1A34715700A44484 /* Products */ = {
isa = PBXGroup;
children = (
B5D808081A34715700A44484 /* JEToolkit.framework */,
B5D8080A1A34715700A44484 /* JEToolkitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -162,10 +295,21 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 2F03A52619C5C6DA005002A5;
productRefGroup = 2F03A53119C5C6DA005002A5 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = B5D806C61A34715700A44484 /* Products */;
ProjectRef = B5D806C51A34715700A44484 /* GCDKit.xcodeproj */;
},
{
ProductGroup = B5D8074F1A34715700A44484 /* Products */;
ProjectRef = B5D8074E1A34715700A44484 /* JEToolkit.xcodeproj */;
},
);
projectRoot = "";
targets = (
2F03A52F19C5C6DA005002A5 /* HardcoreData */,
@@ -174,6 +318,37 @@
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
B5D808021A34715700A44484 /* GCDKit.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = GCDKit.framework;
remoteRef = B5D808011A34715700A44484 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B5D808041A34715700A44484 /* GCDKitTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = GCDKitTests.xctest;
remoteRef = B5D808031A34715700A44484 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B5D808081A34715700A44484 /* JEToolkit.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = JEToolkit.framework;
remoteRef = B5D808071A34715700A44484 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B5D8080A1A34715700A44484 /* JEToolkitTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = JEToolkitTests.xctest;
remoteRef = B5D808091A34715700A44484 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
2F03A52E19C5C6DA005002A5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
@@ -196,6 +371,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5F539901A17A6FC00EC763B /* QueryDescriptor.swift in Sources */,
B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */,
B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */,
B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */,
B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */,
B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */,
2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */,
B5E209E01A0726460089C9D4 /* NSManagedObject+HardcoreData.swift in Sources */,
B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */,
B5D399F119FC818E000E91BB /* DataStack.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -245,7 +430,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -285,7 +470,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -298,25 +483,46 @@
2F03A54419C5C6DA005002A5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
/Users/johnestropia/Documents/XCode,
"Projects/JEToolkit/build/Debug-iphoneos",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/PhotoSearch-dzwflhandxxuvngptapvysfpcwdx/Build/Products/Debug-iphoneos",
/Users/johnestropia/Documents/XCode,
"Projects/JEToolkit/build/Debug-iphoneos",
);
INFOPLIST_FILE = HardcoreData/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
2F03A54519C5C6DA005002A5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
/Users/johnestropia/Documents/XCode,
"Projects/JEToolkit/build/Debug-iphoneos",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/PhotoSearch-dzwflhandxxuvngptapvysfpcwdx/Build/Products/Debug-iphoneos",
/Users/johnestropia/Documents/XCode,
"Projects/JEToolkit/build/Debug-iphoneos",
);
INFOPLIST_FILE = HardcoreData/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -374,6 +580,7 @@
2F03A54519C5C6DA005002A5 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2F03A54619C5C6DA005002A5 /* Build configuration list for PBXNativeTarget "HardcoreDataTests" */ = {
isa = XCConfigurationList;
@@ -382,6 +589,7 @@
2F03A54819C5C6DA005002A5 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};

View File

@@ -0,0 +1,281 @@
//
// DataStack.swift
// HardcoreData
//
// Copyright (c) 2014 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
import GCDKit
/**
The DataStack encapsulates the data model for the Core Data stack. Each DataStack can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own NSPersistentStoreCoordinator, a root NSManagedObjectContext for disk saves, and a shared NSManagedObjectContext acting as a model interface for NSManagedObjects.
*/
public class DataStack: NSObject {
// MARK: - Public
/**
Initializes a DataStack from merged model in the app bundle.
*/
public convenience override init() {
self.init(managedObjectModel: NSManagedObjectModel.mergedModelFromBundles(nil)!)
}
/**
Initializes a DataStack from the specified model name.
:param: modelName the name of the (.xcdatamodeld) model file.
*/
public convenience init(modelName: String) {
let modelFilePath = NSBundle.mainBundle().pathForResource(modelName, ofType: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOfURL: NSURL(fileURLWithPath: modelFilePath)!)!
self.init(managedObjectModel: managedObjectModel)
}
/**
Initializes a DataStack from an NSManagedObjectModel.
:param: modelName the name of the "momd" (or xcdatamodeld) file.
*/
public required init(managedObjectModel: NSManagedObjectModel) {
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.transactionQueue = .createSerial("com.hardcoredata.datastack.transactionqueue")
super.init()
}
/**
Adds an in-memory store to the stack.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
:returns: a PersistentStoreResult indicating success or failure.
*/
public func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult {
let coordinator = self.coordinator;
var persistentStoreError: NSError?
var store: NSPersistentStore?
coordinator.performSynchronously {
store = coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
configuration: configuration,
URL: nil,
options: nil,
error: &persistentStoreError)
}
if let store = store {
return PersistentStoreResult(store)
}
if let error = persistentStoreError {
HardcoreData.handleError(
error,
message: "Failed to add in-memory NSPersistentStore.")
return PersistentStoreResult(error)
}
else {
HardcoreData.handleError(
NSError(hardcoreDataErrorCode: .UnknownError),
message: "Failed to add in-memory NSPersistentStore.")
}
return PersistentStoreResult(.UnknownError)
}
/**
Adds to the stack an SQLite store from the given SQLite file name.
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
:returns: a PersistentStoreResult indicating success or failure.
*/
public func addSQLiteStore(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
return self.addSQLiteStore(
fileURL: NSURL.applicationSupportDirectory().URLByAppendingPathComponent(fileName, isDirectory: false),
configuration: configuration,
automigrating: automigrating,
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure)
}
/**
Adds to the stack an SQLite store from the given SQLite file URL.
:param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
:returns: a PersistentStoreResult indicating success or failure.
*/
public func addSQLiteStore(fileURL: NSURL = NSURL.applicationSupportDirectory().URLByAppendingPathComponent(NSString.applicationName(), isDirectory: true).URLByAppendingPathExtension("sqlite"), configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false)
if store.type == NSSQLiteStoreType
&& isExistingStoreAutomigrating == automigrating {
return PersistentStoreResult(store)
}
HardcoreData.handleError(
NSError(hardcoreDataErrorCode: .DifferentPersistentStoreExistsAtURL),
message: "Failed to add SQLite NSPersistentStore at \"\(fileURL)\" because a different NSPersistentStore at that URL already exists.")
return PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)
}
let fileManager = NSFileManager.defaultManager()
var directoryError: NSError?
if !fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil,
error: &directoryError) {
HardcoreData.handleError(
directoryError!,
message: "Failed to create directory for SQLite store at \"\(fileURL)\".")
return PersistentStoreResult(directoryError!)
}
var store: NSPersistentStore?
var persistentStoreError: NSError?
coordinator.performSynchronously {
store = coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
NSInferMappingModelAutomaticallyOption: true,
NSMigratePersistentStoresAutomaticallyOption: automigrating],
error: &persistentStoreError)
}
if let store = store {
return PersistentStoreResult(store)
}
if let error = persistentStoreError {
if resetStoreOnMigrationFailure
&& (error.code == NSPersistentStoreIncompatibleVersionHashError
|| error.code == NSMigrationMissingSourceModelError)
&& error.domain == NSCocoaErrorDomain {
fileManager.removeItemAtURL(fileURL, error: nil)
fileManager.removeItemAtPath(
fileURL.absoluteString!.stringByAppendingString("-shm"),
error: nil)
fileManager.removeItemAtPath(
fileURL.absoluteString!.stringByAppendingString("-wal"),
error: nil)
var store: NSPersistentStore?
coordinator.performSynchronously {
store = coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
NSInferMappingModelAutomaticallyOption: true,
NSMigratePersistentStoresAutomaticallyOption: automigrating],
error: &persistentStoreError)
}
if let store = store {
return PersistentStoreResult(store)
}
}
}
if let error = persistentStoreError {
HardcoreData.handleError(
error,
message: "Failed to add SQLite NSPersistentStore at \"\(fileURL)\".")
return PersistentStoreResult(error)
}
else {
HardcoreData.handleError(
NSError(hardcoreDataErrorCode: .UnknownError),
message: "Failed to add SQLite NSPersistentStore at \"\(fileURL)\".")
}
return PersistentStoreResult(.UnknownError)
}
/**
Begins a transaction asynchronously where NSManagedObject creates, updates, and deletes can be made.
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent NSManagedObjectContext.
*/
public func performTransaction(closure: (transaction: DataTransaction) -> ()) {
let transaction = DataTransaction(
mainContext: self.mainContext,
queue: self.transactionQueue,
closure: closure)
transaction.perform()
}
/**
Begins a transaction synchronously where NSManagedObject creates, updates, and deletes can be made.
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent NSManagedObjectContext.
:returns: a SaveResult value indicating success or failure.
*/
public func performTransactionAndWait(closure: (transaction: DataTransaction) -> ()) -> SaveResult {
let transaction = DataTransaction(
mainContext: self.mainContext,
queue: self.transactionQueue,
closure: closure)
return transaction.performAndWait()
}
// MARK: - Internal
private let coordinator: NSPersistentStoreCoordinator
private let rootSavingContext: NSManagedObjectContext
private let mainContext: NSManagedObjectContext
private let transactionQueue: GCDQueue;
}

View File

@@ -0,0 +1,162 @@
//
// DataTransaction.swift
// HardcoreData
//
// Copyright (c) 2014 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
import GCDKit
/**
The DataTransaction provides an interface for NSManagedObject creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from DataStack.performTransaction(_:), or from HardcoreData.performTransaction(_:).
*/
public class DataTransaction {
// MARK: - Public
/**
The background concurrent context managed by the transaction.
*/
public let context: NSManagedObjectContext
// MARK: Object management
/**
Creates a new NSManagedObject with the specified entity type. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: entity the NSManagedObject type to be created
:returns: a new NSManagedObject instance of the specified entity type.
*/
public func create<T: NSManagedObject>(entity: T.Type) -> T {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to create an NSManagedObject outside a transaction queue.")
HardcoreData.assert(!self.isTransactionCommited, "Attempted to create an NSManagedObject from an already commited DataTransaction.")
return T.createInContext(self.context)
}
/**
Returns an editable proxy of a specified NSManagedObject. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: object the NSManagedObject type to be edited
:returns: an editable proxy for the specified NSManagedObject.
*/
public func update<T: NSManagedObject>(object: T) -> T? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to update an NSManagedObject outside a transaction queue.")
HardcoreData.assert(!self.isTransactionCommited, "Attempted to update an NSManagedObject from an already commited DataTransaction.")
return object.inContext(self.context)
}
/**
Deletes a specified NSManagedObject. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: object the NSManagedObject type to be deleted
*/
public func delete(object: NSManagedObject) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to delete an NSManagedObject outside a transaction queue.")
HardcoreData.assert(!self.isTransactionCommited, "Attempted to delete an NSManagedObject from an already commited DataTransaction.")
object.deleteFromContext()
}
// MARK: Saving changes
/**
Saves the transaction changes asynchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: completion the block executed after the save completes. Success or failure is reported by the SaveResult argument of the block.
*/
public func commit(completion: (result: SaveResult) -> ()) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a DataTransaction outside a transaction queue.")
HardcoreData.assert(!self.isTransactionCommited, "Attempted to commit a DataTransaction more than once.")
self.isTransactionCommited = true
self.context.saveAsynchronouslyWithCompletion { [weak self] (result) -> () in
self?.result = result
completion(result: result)
}
}
/**
Saves the transaction changes and waits for completion synchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:returns: a SaveResult value indicating success or failure.
*/
public func commitAndWait() -> SaveResult {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a DataTransaction outside a transaction queue.")
HardcoreData.assert(!self.isTransactionCommited, "Attempted to commit a DataTransaction more than once.")
self.isTransactionCommited = true
let result = self.context.saveSynchronously()
self.result = result
return result
}
// MARK: - Internal
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: DataTransaction) -> ()) {
self.mainContext = mainContext
self.transactionQueue = queue
self.context = mainContext.temporaryContext()
self.closure = closure
}
internal func perform() {
self.transactionQueue.barrierAsync {
self.closure(transaction: self)
if !self.isTransactionCommited {
self.commit { (result) -> () in }
}
}
}
internal func performAndWait() -> SaveResult {
self.transactionQueue.barrierSync {
self.closure(transaction: self)
if !self.isTransactionCommited {
self.commitAndWait()
}
}
return self.result!
}
// MARK: - Private
private var isTransactionCommited = false
private var result: SaveResult?
private let mainContext: NSManagedObjectContext
private let transactionQueue: GCDQueue
private let closure: (transaction: DataTransaction) -> ()
}

View File

@@ -2,18 +2,29 @@
// HardcoreData.h
// HardcoreData
//
// Created by John Rommel Estropia on 2014/09/14.
// Copyright (c) 2014 John Rommel Estropia. All rights reserved.
// Copyright (c) 2014 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 <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
//! Project version number for HardcoreData.
FOUNDATION_EXPORT double HardcoreDataVersionNumber;
//! Project version string for HardcoreData.
FOUNDATION_EXPORT const unsigned char HardcoreDataVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <HardcoreData/PublicHeader.h>

View File

@@ -0,0 +1,102 @@
//
// HardcoreData.swift
// HardcoreData
//
// Copyright (c) 2014 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 CoreData
import JEToolkit
/**
HardcoreData - Simple, elegant, and smart Core Data management with Swift
The HardcoreData struct is the main entry point for all other APIs.
*/
public struct HardcoreData {
/**
The default DataStack instance to be used. If defaultStack is not set before the first time accessed, a default-configured DataStack will be created.
Note that changing the defaultStack is not thread safe.
*/
public static var defaultStack = DataStack()
/**
The closure that handles all errors that occur within HardcoreData. The default errorHandler logs errors via JEDumpAlert().
*/
public static var errorHandler = { (error: NSError, message: String, fileName: String, lineNumber: UWord, functionName: StaticString) -> () in
JEDumpAlert(
error,
message,
fileName: fileName,
lineNumber: lineNumber,
functionName: functionName)
}
public static var assertHandler = { (condition: @autoclosure() -> Bool, message: String, fileName: String, lineNumber: UWord, functionName: StaticString) -> () in
JEAssert(
condition,
message,
fileName: fileName,
lineNumber: lineNumber,
functionName: functionName)
}
public static var logHandler = { (message: String, fileName: String, lineNumber: Int32, functionName: String) -> () in
}
/**
Using the defaultStack, begins a transaction asynchronously where NSManagedObject creates, updates, and deletes can be made.
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent NSManagedObjectContext.
*/
public static func performTransaction(closure: (transaction: DataTransaction) -> ()) {
self.defaultStack.performTransaction(closure)
}
/**
Using the defaultStack, begins a transaction asynchronously where NSManagedObject creates, updates, and deletes can be made.
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent NSManagedObjectContext.
:returns: a SaveResult value indicating success or failure.
*/
public static func performTransactionAndWait(closure: (transaction: DataTransaction) -> ()) -> SaveResult {
return self.defaultStack.performTransactionAndWait(closure)
}
internal static func handleError(error: NSError, _ message: String, fileName: String = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) {
self.errorHandler(error, message, fileName.lastPathComponent, lineNumber, functionName)
}
internal static func assert(condition: @autoclosure() -> Bool, _ message: String, fileName: String = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) {
self.assertHandler(condition, message, fileName.lastPathComponent, lineNumber, functionName)
}
}

21
HardcoreData/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 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.

View File

@@ -0,0 +1,73 @@
//
// NSError+HardcoreData.swift
// HardcoreData
//
// Copyright (c) 2014 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
/**
The NSError error domain for HardcoreData.
*/
public let HardcoreDataErrorDomain = "com.hardcoredata.error"
/**
The NSError error codes for HardcoreDataErrorDomain.
*/
public enum HardcoreDataErrorCode: Int {
/**
A failure occured because of an unknown error.
*/
case UnknownError
/**
The NSPersistentStore could note be initialized because another store existed at the specified NSURL.
*/
case DifferentPersistentStoreExistsAtURL
}
public extension NSError {
/**
If the error's domain is HardcoreDataErrorDomain, returns the associated HardcoreDataErrorCode. For other domains, returns nil.
*/
public var hardcoreDataErrorCode: HardcoreDataErrorCode? {
return (self.domain == HardcoreDataErrorDomain
? HardcoreDataErrorCode(rawValue: self.code)
: nil)
}
internal convenience init(hardcoreDataErrorCode: HardcoreDataErrorCode) {
self.init(hardcoreDataErrorCode: hardcoreDataErrorCode, userInfo: nil)
}
internal convenience init(hardcoreDataErrorCode: HardcoreDataErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(
domain: HardcoreDataErrorDomain,
code: hardcoreDataErrorCode.rawValue,
userInfo: userInfo)
}
}

View File

@@ -0,0 +1,73 @@
//
// NSManagedObject+HardcoreData.swift
// HardcoreData
//
// Copyright (c) 2014 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
public extension NSManagedObject {
public class var entityName: String {
return self.className().componentsSeparatedByString(".").last!
}
public class func createInContext(context: NSManagedObjectContext) -> Self {
return self(entity: NSEntityDescription.entityForName(self.entityName, inManagedObjectContext: context)!,
insertIntoManagedObjectContext: context)
}
public func inContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
let objectID = self.objectID
if objectID.temporaryID {
var permanentIDError: NSError?
if !context.obtainPermanentIDsForObjects([self], error: &permanentIDError) {
HardcoreData.handleError(
permanentIDError!,
message: "Failed to obtain permanent ID for object.")
return nil
}
}
var existingObjectError: NSError?
if let existingObject = context.existingObjectWithID(objectID, error: &existingObjectError) {
return (existingObject as T)
}
HardcoreData.handleError(
existingObjectError!,
message: "Failed to load existing NSManagedObject in context.")
return nil;
}
public func deleteFromContext() {
self.managedObjectContext?.deleteObject(self)
}
}

View File

@@ -0,0 +1,302 @@
//
// NSManagedObjectContext+HardcoreData.swift
// HardcoreData
//
// Copyright (c) 2014 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
import GCDKit
private var _HardcoreData_NSManagedObjectContext_shouldCascadeSavesToParent: Void?
public extension NSManagedObjectContext {
// MARK: - Public
// MARK: Transactions
public func temporaryContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.parentContext = self
context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext")
context.shouldCascadeSavesToParent = true
return context
}
// MARK: Querying
public func findFirst<T: NSManagedObject>(entity: T.Type) -> T? {
return self.findFirst(T.self, predicate: NSPredicate(value: true))
}
public func findFirst<T: NSManagedObject>(entity: T.Type, predicate: NSPredicate) -> T? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName(
entity.entityName,
inManagedObjectContext: self)
fetchRequest.fetchLimit = 1
var fetchResults: [T]?
self.performBlockAndWait {
var error: NSError?
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T]
if fetchResults == nil {
HardcoreData.handleError(
error!,
message: "Failed executing fetch request.")
}
}
return fetchResults?.first
}
public func findAll<T: NSManagedObject>(entity: T.Type) -> [T]? {
return self.findAll(QueryDescriptor<T>(entity: entity))
}
public func findAll<T: NSManagedObject>(query: QueryDescriptor<T>) -> [T]? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName(
query.entityName,
inManagedObjectContext: self)
var fetchResults: [T]?
self.performBlockAndWait {
var error: NSError?
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T]
if fetchResults == nil {
HardcoreData.handleError(
error!,
message: "Failed executing fetch request.")
}
}
return fetchResults
}
// MARK: - Internal
internal func saveSynchronously() -> SaveResult {
var result: SaveResult = SaveResult(hasChanges: false)
if !self.hasChanges {
self.reset()
return result
}
self.performBlockAndWait {
[unowned self] () -> () in
var saveError: NSError?
if self.save(&saveError) {
if self.shouldCascadeSavesToParent {
if let parentContext = self.parentContext {
switch parentContext.saveSynchronously() {
case .Success(let hasChanges):
result = SaveResult(hasChanges: true)
case .Failure(let error):
result = SaveResult(error)
}
return
}
}
result = SaveResult(hasChanges: true)
}
else if let error = saveError {
HardcoreData.handleError(
error,
message: "Failed to save NSManagedObjectContext.")
result = SaveResult(error)
}
else {
result = SaveResult(hasChanges: false)
}
}
return result
}
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> ())?) {
if !self.hasChanges {
self.reset()
if let completion = completion {
GCDBlock.async(.Main) {
completion(result: SaveResult(hasChanges: false))
}
}
return
}
self.performBlock {
[unowned self] () -> () in
var saveError: NSError?
if self.save(&saveError) {
if self.shouldCascadeSavesToParent {
if let parentContext = self.parentContext {
parentContext.saveAsynchronouslyWithCompletion(completion)
return
}
}
if let completion = completion {
GCDBlock.async(.Main) {
completion(result: SaveResult(hasChanges: true))
}
}
}
else if let error = saveError {
HardcoreData.handleError(
error,
message: "Failed to save NSManagedObjectContext.")
if let completion = completion {
GCDBlock.async(.Main) {
completion(result: SaveResult(error))
}
}
}
else if let completion = completion {
GCDBlock.async(.Main) {
completion(result: SaveResult(hasChanges: false))
}
}
}
}
internal class func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.setupForHardcoreDataWithContextName("com.hardcoredata.rootcontext")
return context
}
internal class func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.parentContext = rootContext
context.setupForHardcoreDataWithContextName("com.hardcoredata.maincontext")
context.shouldCascadeSavesToParent = true
context.registerForNotificationsWithName(
NSManagedObjectContextDidSaveNotification,
fromObject: rootContext,
targetQueue: NSOperationQueue.mainQueue()) {
[unowned context] (note) -> () in
context.mergeChangesFromContextDidSaveNotification(note)
}
return context
}
// MARK: - Private
private var shouldCascadeSavesToParent: Bool {
get {
if let value = objc_getAssociatedObject(self, &_HardcoreData_NSManagedObjectContext_shouldCascadeSavesToParent) as? NSNumber {
return value.boolValue
}
return false
}
set {
objc_setAssociatedObject(
self,
&_HardcoreData_NSManagedObjectContext_shouldCascadeSavesToParent,
newValue,
objc_AssociationPolicy(OBJC_ASSOCIATION_ASSIGN))
}
}
private func setupForHardcoreDataWithContextName(contextName: String) {
if self.respondsToSelector("setName:") {
self.name = contextName
}
self.registerForNotificationsWithName(NSManagedObjectContextWillSaveNotification, fromObject: self) {
(note) -> () in
let context: NSManagedObjectContext = note.object as NSManagedObjectContext
let insertedObjects = context.insertedObjects
if insertedObjects.count <= 0 {
return
}
var permanentIDError: NSError?
if context.obtainPermanentIDsForObjects(insertedObjects.allObjects, error: &permanentIDError) {
return
}
if let error = permanentIDError {
HardcoreData.handleError(
error,
message: "Failed to obtain permanent IDs for inserted objects.")
}
}
}
}

View File

@@ -0,0 +1,44 @@
//
// NSPersistentStoreCoordinator+HardcoreData.swift
// HardcoreData
//
// Copyright (c) 2014 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
public extension NSPersistentStoreCoordinator {
public func performSynchronously(closure: () -> ()) {
if self.respondsToSelector("performBlockAndWait:") {
self.performBlockAndWait(closure)
}
else {
self.lock()
autoreleasepool(closure)
self.unlock()
}
}
}

View File

@@ -0,0 +1,66 @@
//
// PersistentStoreResult.swift
// HardcoreData
//
// Copyright (c) 2014 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
public enum PersistentStoreResult {
case Success(NSPersistentStore)
case Failure(NSError)
internal init(_ store: NSPersistentStore) {
self = .Success(store)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: HardcoreDataErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: HardcoreDataErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(
hardcoreDataErrorCode: errorCode,
userInfo: userInfo))
}
}
extension PersistentStoreResult: BooleanType {
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -0,0 +1,30 @@
//
// QueryDescriptor.swift
// HardcoreData
//
// Created by John Rommel Estropia on 14/11/16.
// Copyright (c) 2014 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreData
public struct QueryDescriptor<T: NSManagedObject> {
public var entityName: String {
return self.entity.entityName
}
// MARK: Internal
internal init(entity: T.Type) {
self.entity = entity
}
// MARK: Private
private let entity: T.Type
private var predicate: NSPredicate?
private var sortDescriptors: [NSSortDescriptor]?
}

View File

@@ -0,0 +1,65 @@
//
// SaveResult.swift
// HardcoreData
//
// Copyright (c) 2014 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
public enum SaveResult {
case Success(hasChanges: Bool)
case Failure(NSError)
internal init(hasChanges: Bool) {
self = .Success(hasChanges: hasChanges)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: HardcoreDataErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: HardcoreDataErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(
hardcoreDataErrorCode: errorCode,
userInfo: userInfo))
}
}
extension SaveResult: BooleanType {
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -23,7 +23,38 @@ class HardcoreDataTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
XCTAssert(true, "Pass")
#if DEBUG
let resetStoreOnMigrationFailure = true
#else
let resetStoreOnMigrationFailure = false
#endif
switch HardcoreData.defaultStack.addSQLiteStore(resetStoreOnMigrationFailure: resetStoreOnMigrationFailure) {
case .Failure(let error):
NSException(
name: "CoreDataMigrationException",
reason: error.localizedDescription,
userInfo: error.userInfo).raise()
default: break
}
HardcoreData.performTransaction { (transaction) -> () in
let obj = transaction.context.findFirst(FlickrPhoto)
transaction.commit { (result) -> () in
switch result {
case .Success(let hasChanges):
JEDump(hasChanges, "hasChanges")
case .Failure(let error):
JEDump(error, "error")
}
}
}
}
func testPerformanceExample() {

1
Libraries/GCDKit Submodule

Submodule Libraries/GCDKit added at 5a671ab641

1
Libraries/JEToolkit Submodule

Submodule Libraries/JEToolkit added at 5f2a2b3775