mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-12 20:30:30 +01:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4171de86e | ||
|
|
b0a637520e | ||
|
|
e8f17cb33a | ||
|
|
3c29cdf2d2 | ||
|
|
e462ae5a19 | ||
|
|
6ed6159eec | ||
|
|
7710578bbc | ||
|
|
6727c39e21 | ||
|
|
6aa24cef2d | ||
|
|
a6f3c0bf95 | ||
|
|
869660d41d | ||
|
|
6b8d5df049 | ||
|
|
1ca3ba5407 | ||
|
|
4da81250ec |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "0.1.0"
|
||||
s.version = "0.1.2"
|
||||
s.license = "MIT"
|
||||
s.summary = "Simple, elegant, and smart Core Data programming with Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
|
||||
@@ -80,6 +80,13 @@
|
||||
remoteGlobalIDString = 2FBBCAD619A9FE610070E4AB;
|
||||
remoteInfo = GCDKitTests;
|
||||
};
|
||||
B5D9C9081B20A87D00E64F0E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B5D806C51A34715700A44484 /* GCDKit.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2FBBCACA19A9FE610070E4AB;
|
||||
remoteInfo = GCDKit;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -396,6 +403,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
B5D9C9091B20A87D00E64F0E /* PBXTargetDependency */,
|
||||
);
|
||||
name = CoreStore;
|
||||
productName = CoreStore;
|
||||
@@ -563,6 +571,11 @@
|
||||
target = 2F03A52F19C5C6DA005002A5 /* CoreStore */;
|
||||
targetProxy = B5D372871A39CF4D00F583D9 /* PBXContainerItemProxy */;
|
||||
};
|
||||
B5D9C9091B20A87D00E64F0E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = GCDKit;
|
||||
targetProxy = B5D9C9081B20A87D00E64F0E /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>../..</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>../..Libraries/GCDKit/</string>
|
||||
<string>../..Libraries/GCDKit</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:JohnEstropia/CoreStore.git</string>
|
||||
|
||||
@@ -27,12 +27,6 @@ import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
/**
|
||||
Okay, okay. This one's shorter.
|
||||
*/
|
||||
public typealias HCD = CoreStore
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,22 +46,22 @@ public struct From<T: NSManagedObject> {
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ configurations: String...) {
|
||||
public init(_ configurations: String?...) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ configurations: [String]) {
|
||||
public init(_ configurations: [String?]) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: String...) {
|
||||
public init(_ entity: T.Type, _ configurations: String?...) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: [String]) {
|
||||
public init(_ entity: T.Type, _ configurations: [String?]) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
}
|
||||
@@ -120,9 +120,9 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
|
||||
private init(configurations: [String]) {
|
||||
private init(configurations: [String?]) {
|
||||
|
||||
let configurationsSet = Set(configurations)
|
||||
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter {
|
||||
|
||||
@@ -31,6 +31,11 @@ public func +(left: OrderBy, right: OrderBy) -> OrderBy {
|
||||
return OrderBy(left.sortDescriptors + right.sortDescriptors)
|
||||
}
|
||||
|
||||
public func +=(inout left: OrderBy, right: OrderBy) {
|
||||
|
||||
left = left + right
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath
|
||||
|
||||
|
||||
@@ -108,7 +108,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
return ._Aggregate(
|
||||
function: "average:",
|
||||
keyPath,
|
||||
As: alias ?? "average(\(keyPath))"
|
||||
As: alias ?? "average(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -129,7 +130,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
return ._Aggregate(
|
||||
function: "count:",
|
||||
keyPath,
|
||||
As: alias ?? "count(\(keyPath))"
|
||||
As: alias ?? "count(\(keyPath))",
|
||||
nativeType: .Integer64AttributeType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,28 +152,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
return ._Aggregate(
|
||||
function: "max:",
|
||||
keyPath,
|
||||
As: alias ?? "max(\(keyPath))"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the median value for an attribute.
|
||||
|
||||
let medianAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.Median("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the median value for an attribute
|
||||
*/
|
||||
public static func Median(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "median:",
|
||||
keyPath, As:
|
||||
alias ?? "median(\(keyPath))"
|
||||
As: alias ?? "max(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -180,7 +162,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
let minimumAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.Median("age"))
|
||||
Select<Int>(.Minimum("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
@@ -192,28 +174,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
return ._Aggregate(
|
||||
function: "min:",
|
||||
keyPath,
|
||||
As: alias ?? "min(\(keyPath))"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the standard deviation value for an attribute.
|
||||
|
||||
let stddevAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.StandardDeviation("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "stddev(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the standard deviation value for an attribute
|
||||
*/
|
||||
public static func StandardDeviation(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "stddev:",
|
||||
keyPath,
|
||||
As: alias ?? "stddev(\(keyPath))"
|
||||
As: alias ?? "min(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -234,7 +196,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
return ._Aggregate(
|
||||
function: "sum:",
|
||||
keyPath,
|
||||
As: alias ?? "sum(\(keyPath))"
|
||||
As: alias ?? "sum(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
@@ -260,7 +223,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
// MARK: Internal
|
||||
|
||||
case _Attribute(KeyPath)
|
||||
case _Aggregate(function: String, KeyPath, As: String)
|
||||
case _Aggregate(function: String, KeyPath, As: String, nativeType: NSAttributeType)
|
||||
}
|
||||
|
||||
|
||||
@@ -358,12 +321,19 @@ public struct Select<T: SelectResultType> {
|
||||
CoreStore.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by \(typeName(self)) query clause.")
|
||||
}
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias):
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
if let attributeDescription = attributesByName[keyPath] as? NSAttributeDescription {
|
||||
|
||||
let expressionDescription = NSExpressionDescription()
|
||||
expressionDescription.name = alias
|
||||
expressionDescription.expressionResultType = attributeDescription.attributeType
|
||||
if nativeType == .UndefinedAttributeType {
|
||||
|
||||
expressionDescription.expressionResultType = attributeDescription.attributeType
|
||||
}
|
||||
else {
|
||||
|
||||
expressionDescription.expressionResultType = nativeType
|
||||
}
|
||||
expressionDescription.expression = NSExpression(
|
||||
forFunction: function,
|
||||
arguments: [NSExpression(forKeyPath: keyPath)]
|
||||
@@ -388,7 +358,7 @@ public struct Select<T: SelectResultType> {
|
||||
case ._Attribute(let keyPath):
|
||||
return keyPath
|
||||
|
||||
case ._Aggregate(_, _, let alias):
|
||||
case ._Aggregate(_, _, let alias, _):
|
||||
return alias
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<string>0.1.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -41,6 +41,11 @@ internal extension NSManagedObject {
|
||||
)
|
||||
}
|
||||
|
||||
internal class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context, objectID: objectID)
|
||||
}
|
||||
|
||||
internal func inContext(context: NSManagedObjectContext) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context)
|
||||
@@ -54,6 +59,20 @@ internal extension NSManagedObject {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private class func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
var error: NSError?
|
||||
if let existingObject = context.existingObjectWithID(objectID, error: &error) {
|
||||
|
||||
return (existingObject as! T)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load existing \(typeName(self)) in context.")
|
||||
return nil;
|
||||
}
|
||||
|
||||
private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
|
||||
|
||||
let objectID = self.objectID
|
||||
|
||||
@@ -36,6 +36,8 @@ The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
|
||||
- The `assert(...)` method calls `assert(...)` on the arguments.
|
||||
*/
|
||||
public final class DefaultLogger: CoreStoreLogger {
|
||||
|
||||
public init() { }
|
||||
|
||||
public func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
|
||||
@@ -109,11 +109,25 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public override func fetch<T: NSManagedObject>(object: T?) -> T? {
|
||||
public override func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).")
|
||||
|
||||
return super.fetch(object)
|
||||
return super.edit(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).")
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +142,32 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
|
||||
super.delete([object1, object2] + objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,11 @@ public struct Into<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
internal static var defaultConfigurationName: String {
|
||||
|
||||
return "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause.
|
||||
Sample Usage:
|
||||
@@ -180,13 +185,28 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public func fetch<T: NSManagedObject>(object: T?) -> T? {
|
||||
public func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.")
|
||||
|
||||
return object?.inContext(self.context)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.")
|
||||
CoreStore.assert(into.inferStoreIfPossible || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, "Attempted to update an entity of type <\(T.self)> but the specified persistent store do not match the `NSManagedObjectID`.")
|
||||
|
||||
return T.inContext(self.context, withObjectID: objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`.
|
||||
|
||||
@@ -194,11 +214,39 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
*/
|
||||
public func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type \(typeName(object)) outside its designated queue.")
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity outside its designated queue.")
|
||||
|
||||
object?.inContext(self.context)?.deleteFromContext()
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
self.delete([object1, object2] + objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
*/
|
||||
public func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete entities outside their designated queue.")
|
||||
|
||||
let context = self.context
|
||||
for object in objects {
|
||||
|
||||
object?.inContext(context)?.deleteFromContext()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Saving changes
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,11 +88,25 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public override func fetch<T: NSManagedObject>(object: T?) -> T? {
|
||||
public override func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).")
|
||||
|
||||
return super.fetch(object)
|
||||
return super.edit(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).")
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +121,32 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
|
||||
super.delete([object1, object2] + objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
|
||||
@@ -28,8 +28,6 @@ import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
private let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
|
||||
private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL
|
||||
|
||||
private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData")
|
||||
@@ -185,7 +183,7 @@ public final class DataStack {
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating == automigrating
|
||||
&& store.configurationName == (configuration ?? defaultConfigurationName) {
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
return PersistentStoreResult(store)
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@
|
||||
B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; };
|
||||
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; };
|
||||
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; };
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; };
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; };
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; };
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5E7240F1B11F993006FB83F /* TwitterAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E7240E1B11F993006FB83F /* TwitterAccount.swift */; };
|
||||
B5E724111B11F994006FB83F /* FacebookAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E724101B11F994006FB83F /* FacebookAccount.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -67,6 +70,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */,
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@@ -93,8 +97,9 @@
|
||||
B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
|
||||
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
|
||||
B583A9251AF5F547001F76AF /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = GCDKit.framework; path = "/Users/johnestropia/Library/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = "<absolute>"; };
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5E7240E1B11F993006FB83F /* TwitterAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterAccount.swift; sourceTree = "<group>"; };
|
||||
B5E724101B11F994006FB83F /* FacebookAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FacebookAccount.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -106,6 +111,7 @@
|
||||
files = (
|
||||
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */,
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -138,9 +144,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */,
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */,
|
||||
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
|
||||
B52977DE1B120F83003D50A5 /* MapKit.framework */,
|
||||
B583A9251AF5F547001F76AF /* GCDKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -169,6 +175,7 @@
|
||||
B566E3271B117AE700F4F0C6 /* Stack Setup Demo */,
|
||||
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
|
||||
B52977DB1B120F2C003D50A5 /* Transactions Demo */,
|
||||
B56964C61B20AC200075EE4A /* Loggers Demo */,
|
||||
B54AAD571AF4D26E00848AE0 /* Main.storyboard */,
|
||||
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */,
|
||||
B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */,
|
||||
@@ -197,6 +204,14 @@
|
||||
path = "Stack Setup Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B56964C61B20AC200075EE4A /* Loggers Demo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */,
|
||||
);
|
||||
path = "Loggers Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B583A9151AF5F4F3001F76AF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -306,6 +321,7 @@
|
||||
B52977E41B121635003D50A5 /* Place.swift in Sources */,
|
||||
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */,
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */,
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */,
|
||||
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */,
|
||||
B5E724111B11F994006FB83F /* FacebookAccount.swift in Sources */,
|
||||
B5E7240F1B11F993006FB83F /* TwitterAccount.swift in Sources */,
|
||||
@@ -441,6 +457,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos",
|
||||
);
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
@@ -456,6 +473,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos",
|
||||
);
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>../../..</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>../../../Libraries/GCDKit/</string>
|
||||
<string>../../../Libraries/GCDKit</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:JohnEstropia/CoreStore.git</string>
|
||||
|
||||
@@ -20,6 +20,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
|
||||
application.statusBarStyle = .LightContent
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<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">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -14,17 +14,17 @@
|
||||
<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">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStoreDemo" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--SNS Accounts-->
|
||||
@@ -19,25 +20,25 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="sns" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="czc-nd-es9">
|
||||
<rect key="frame" x="20" y="20" width="560" height="21.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="20" y="20" width="560" height="26.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="name" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1eh-91-O2N">
|
||||
<rect key="frame" x="20" y="65" width="42.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="20" y="70" width="41" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="friends" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p2y-0T-hQs">
|
||||
<rect key="frame" x="536" y="67" width="44" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="539.5" y="72" width="40.5" height="17"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="1eh-91-O2N" firstAttribute="top" secondItem="czc-nd-es9" secondAttribute="bottom" constant="23.5" id="F6q-Jt-glI"/>
|
||||
<constraint firstItem="p2y-0T-hQs" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1eh-91-O2N" secondAttribute="trailing" constant="20" id="GVS-6o-rmj"/>
|
||||
@@ -57,13 +58,13 @@
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
@@ -91,10 +92,10 @@
|
||||
<scene sceneID="0Be-vc-h1W">
|
||||
<objects>
|
||||
<tableViewController id="t0d-B0-B7U" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="10" sectionFooterHeight="10" id="uHB-Yr-ujV">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="50" sectionHeaderHeight="10" sectionFooterHeight="10" id="uHB-Yr-ujV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<sections>
|
||||
<tableViewSection id="wIP-Hn-YfF">
|
||||
<cells>
|
||||
@@ -105,14 +106,14 @@
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -128,14 +129,14 @@
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -151,14 +152,14 @@
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemark" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -172,20 +173,23 @@
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW">
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="5yy-0N-QDU" kind="show" id="5D7-Xp-1HT"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -194,14 +198,14 @@
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -246,25 +250,25 @@
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
|
||||
<rect key="frame" x="16" y="154" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
|
||||
<rect key="frame" x="16" y="192" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
|
||||
<rect key="frame" x="16" y="230" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
|
||||
@@ -288,7 +292,7 @@
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
|
||||
<rect key="frame" x="16" y="49" width="568" height="21"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -399,6 +403,12 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="00L-5k-Eno">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="24"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
@@ -430,9 +440,9 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HJC-5w-lIN">
|
||||
<rect key="frame" x="45" y="8" width="37" height="27"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -488,9 +498,9 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zyu-PC-WmO">
|
||||
<rect key="frame" x="45" y="8" width="37" height="27"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -533,10 +543,11 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6XA-6M-yvZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.75362819430000005" green="0.91446238759999998" blue="0.99948346610000005" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="barTintColor" red="0.49803921580314636" green="0.49803921580314636" blue="0.49803921580314636" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="tintColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -560,8 +571,8 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="jPl-fH-NlD" id="Sjn-YC-haS"/>
|
||||
</connections>
|
||||
@@ -585,6 +596,65 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3694" y="2020"/>
|
||||
</scene>
|
||||
<!--Logger-->
|
||||
<scene sceneID="n7W-0g-bbY">
|
||||
<objects>
|
||||
<viewController automaticallyAdjustsScrollViewInsets="NO" id="5yy-0N-QDU" customClass="CustomLoggerViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="pvA-Nu-u2P"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Cwn-Jd-4Lr"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="n9M-Je-Dj0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" alwaysBounceVertical="YES" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TpK-gX-CTN">
|
||||
<rect key="frame" x="0.0" y="142" width="600" height="458"/>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.94509803921568625" green="0.7686274509803922" blue="0.058823529411764705" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" name="AppleSDGothicNeo-Regular" family="Apple SD Gothic Neo" pointSize="15"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" momentary="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4iq-B4-k0p">
|
||||
<rect key="frame" x="20" y="94" width="560" height="29"/>
|
||||
<segments>
|
||||
<segment title="Log"/>
|
||||
<segment title="Error"/>
|
||||
<segment title="Assert"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="segmentedControlValueChanged:" destination="5yy-0N-QDU" eventType="valueChanged" id="2pp-Vt-2Os"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="4iq-B4-k0p" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" constant="20" id="6SM-eN-NxP"/>
|
||||
<constraint firstItem="TpK-gX-CTN" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" id="AAw-IG-taz"/>
|
||||
<constraint firstItem="TpK-gX-CTN" firstAttribute="height" secondItem="n9M-Je-Dj0" secondAttribute="height" multiplier="0.5" id="Du2-yY-yfP"/>
|
||||
<constraint firstItem="4iq-B4-k0p" firstAttribute="top" secondItem="pvA-Nu-u2P" secondAttribute="bottom" constant="30" id="Mco-Qt-OkH"/>
|
||||
<constraint firstItem="TpK-gX-CTN" firstAttribute="top" secondItem="4iq-B4-k0p" secondAttribute="bottom" constant="20" id="tMF-ER-rYX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4iq-B4-k0p" secondAttribute="trailing" constant="20" id="uBb-jQ-c4V"/>
|
||||
<constraint firstItem="Cwn-Jd-4Lr" firstAttribute="top" secondItem="TpK-gX-CTN" secondAttribute="bottom" id="vSD-vy-P5P"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TpK-gX-CTN" secondAttribute="trailing" id="xit-v8-6QP"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="Du2-yY-yfP"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Logger" id="hD2-by-4fO"/>
|
||||
<connections>
|
||||
<outlet property="segmentedControl" destination="4iq-B4-k0p" id="aJT-Tr-QRX"/>
|
||||
<outlet property="textView" destination="TpK-gX-CTN" id="keL-wF-b90"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="gKf-zW-DSX" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3694" y="2792"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="3ih-RN-P43">
|
||||
<objects>
|
||||
@@ -594,10 +664,11 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wJo-mp-1pS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.7536281943321228" green="0.9144623875617981" blue="0.99948346614837646" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="barTintColor" red="0.49803921579999999" green="0.49803921579999999" blue="0.49803921579999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="tintColor" red="0.74117647058823533" green="0.76470588235294112" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</configuration>
|
||||
<elements>
|
||||
<element name="Palette" positionX="261" positionY="189" width="128" height="105"/>
|
||||
<element name="UserAccount" positionX="261" positionY="216" width="128" height="90"/>
|
||||
<element name="Place" positionX="261" positionY="225" width="128" height="105"/>
|
||||
<element name="UserAccount" positionX="261" positionY="216" width="128" height="90"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.johnestropia.HardcoreDataDemo</string>
|
||||
<string>com.johnestropia.CoreStoreDemo</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -30,6 +30,8 @@
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UIStatusBarTintParameters</key>
|
||||
<dict>
|
||||
<key>UINavigationBar</key>
|
||||
@@ -44,5 +46,7 @@
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -123,7 +123,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let hue = self.hueSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.fetch(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
|
||||
palette.hue = Int32(hue)
|
||||
transaction.commit()
|
||||
@@ -136,7 +136,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let saturation = self.saturationSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.fetch(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
|
||||
palette.saturation = saturation
|
||||
transaction.commit()
|
||||
@@ -149,7 +149,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let brightness = self.brightnessSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.fetch(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
|
||||
palette.brightness = brightness
|
||||
transaction.commit()
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// CustomLoggerViewController.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - CustomLoggerViewController
|
||||
|
||||
class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
deinit {
|
||||
|
||||
CoreStore.logger = DefaultLogger()
|
||||
}
|
||||
|
||||
let dataStack = DataStack()
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.dataStack.addSQLiteStore("emptyStore.sqlite")
|
||||
CoreStore.logger = self
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Logger Demo",
|
||||
message: "This demo shows how to plug-in any logging framework to CoreStore.\n\nThe view controller implements CoreStoreLogger and appends all logs to the text view.",
|
||||
preferredStyle: .Alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreLogger
|
||||
|
||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
let levelString: String
|
||||
switch level {
|
||||
|
||||
case .Trace: levelString = "Trace"
|
||||
case .Notice: levelString = "Notice"
|
||||
case .Warning: levelString = "Warning"
|
||||
case .Fatal: levelString = "Fatal"
|
||||
}
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@IBOutlet dynamic weak var textView: UITextView?
|
||||
@IBOutlet dynamic weak var segmentedControl: UISegmentedControl?
|
||||
|
||||
@IBAction dynamic func segmentedControlValueChanged(sender: AnyObject?) {
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(0):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.create(Into(UserAccount))
|
||||
}
|
||||
|
||||
case .Some(1):
|
||||
self.dataStack.addSQLiteStore("emptyStore.sqlite", configuration: "invalidStore")
|
||||
|
||||
case .Some(2):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.commit()
|
||||
transaction.commit()
|
||||
}
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,9 +168,16 @@ class StackSetupDemoViewController: UITableViewController {
|
||||
|
||||
switch section {
|
||||
|
||||
case 0: return "Facebook Accounts"
|
||||
case 1: return "Twitter Accounts"
|
||||
default: return nil
|
||||
case 0:
|
||||
let count = Static.facebookStack.fetchCount(From(UserAccount)) ?? 0
|
||||
return "Facebook Accounts (\(count) users)"
|
||||
|
||||
case 1:
|
||||
let count = Static.twitterStack.fetchCount(From(UserAccount)) ?? 0
|
||||
return "Twitter Accounts (\(count) users)"
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let place = transaction.fetch(Static.placeController.object)
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.coordinate = mapView.convertPoint(
|
||||
gesture.locationInView(mapView),
|
||||
toCoordinateFromView: mapView
|
||||
@@ -175,7 +175,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let place = transaction.fetch(Static.placeController.object)
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.setInitialValues()
|
||||
transaction.commit()
|
||||
}
|
||||
@@ -195,7 +195,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
|
||||
|
||||
if let strongSelf = self, let placemark = (placemarks as? [CLPlacemark])?.first {
|
||||
|
||||
let place = transaction.fetch(Static.placeController.object)
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.title = placemark.name
|
||||
place?.subtitle = ABCreateStringWithAddressDictionary(placemark.addressDictionary, true)
|
||||
transaction.commit { (_) -> Void in }
|
||||
|
||||
@@ -166,10 +166,12 @@ class CoreStoreTests: XCTestCase {
|
||||
let obj1 = transaction.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(obj1, "obj1 != nil")
|
||||
|
||||
var orderBy = OrderBy(.Ascending("testEntityID"))
|
||||
orderBy += OrderBy(.Descending("testString"))
|
||||
let objs2 = transaction.fetchAll(
|
||||
From(TestEntity2),
|
||||
Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90),
|
||||
OrderBy(.Ascending("testEntityID"), .Descending("testString")),
|
||||
orderBy,
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = true
|
||||
@@ -182,7 +184,7 @@ class CoreStoreTests: XCTestCase {
|
||||
|
||||
let counts = CoreStore.queryAttributes(
|
||||
From(TestEntity2),
|
||||
Select(.Count("testString", As: "count"), "testString"),
|
||||
Select("testString", .Count("testString", As: "count")),
|
||||
GroupBy("testString")
|
||||
)
|
||||
println(counts)
|
||||
@@ -253,6 +255,17 @@ class CoreStoreTests: XCTestCase {
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges, "hasChanges == true")
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj5Copy1 = transaction.edit(obj5)
|
||||
XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID")
|
||||
XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1")
|
||||
|
||||
let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID)
|
||||
XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID")
|
||||
XCTAssertFalse(obj5 == obj5Copy2, "obj5 == obj5Copy2")
|
||||
}
|
||||
|
||||
let count: Int? = CoreStore.queryValue(
|
||||
From(TestEntity1),
|
||||
Select(.Count("testNumber"))
|
||||
@@ -279,6 +292,22 @@ class CoreStoreTests: XCTestCase {
|
||||
)
|
||||
XCTAssertTrue(count == 2, "count == 2 (actual: \(count))")
|
||||
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj6 = transaction.edit(obj6)
|
||||
let obj5 = transaction.edit(obj5)
|
||||
transaction.delete(obj5, obj6)
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
|
||||
let count2 = CoreStore.queryValue(
|
||||
From(TestEntity1),
|
||||
Select<Int>(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
|
||||
|
||||
detachedExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
|
||||
663
README.md
663
README.md
@@ -1,22 +1,26 @@
|
||||
# CoreStore
|
||||
[](http://cocoadocs.org/docsets/CoreStore)
|
||||
[](http://cocoadocs.org/docsets/CoreStore)
|
||||
[](http://cocoadocs.org/docsets/CoreStore)
|
||||
[](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE)
|
||||
|
||||
Simple, elegant, and smart Core Data programming with Swift
|
||||
(Swift, iOS 8+)
|
||||
|
||||
[Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki)
|
||||
|
||||
|
||||
## Features
|
||||
- Supports multiple persistent stores per *data stack*, just the way .xcdatamodeld files are supposed to. CoreStore will also manage one *data stack* by default, but you can create and manage as many as you need.
|
||||
- Ability to plug-in your own logging framework (or any of your favorite 3rd-party logger)
|
||||
- Gets around a limitation with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the .xcdatamodeld file, so you are free to name them independently.
|
||||
- Observe a list of `NSManagedObject`'s using `ManagedObjectListController`, a clean wrapper for `NSFetchedResultsController`. Another controller, `ManagedObjectController`, lets you observe changes for a single object without using KVO. Both controllers can have multiple observers as well, so there is no extra overhead when sharing the same data source for multiple screens.
|
||||
## What CoreStore does better:
|
||||
- Heavily supports multiple persistent stores per data stack, just the way .xcdatamodeld files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
|
||||
- Ability to plug-in your own logging framework
|
||||
- Gets around a limitation with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you are free to name them independently.
|
||||
- Provides type-safe, easy to configure observers to replace `NSFetchedResultsController` and KVO
|
||||
- Exposes API not just for fetching, but also for querying aggregates and property values
|
||||
- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability.
|
||||
- Provides convenient API for common use cases.
|
||||
- Clean API designed around Swift’s code elegance and type safety.
|
||||
|
||||
**CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that prioritizes readability, consistency, and safety.**
|
||||
|
||||
#### TL;DR sample codes
|
||||
|
||||
Quick-setup:
|
||||
@@ -27,9 +31,9 @@ CoreStore.addSQLiteStore("MyStore.sqlite")
|
||||
Simple transactions:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let object = transaction.create(MyEntity)
|
||||
object.entityID = 1
|
||||
object.name = "test entity"
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
person.name = "John Smith"
|
||||
person.age = 42
|
||||
|
||||
transaction.commit { (result) -> Void in
|
||||
switch result {
|
||||
@@ -42,29 +46,31 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
Easy fetching:
|
||||
```swift
|
||||
let objects = CoreStore.fetchAll(From(MyEntity))
|
||||
let people = CoreStore.fetchAll(From(MyPersonEntity))
|
||||
```
|
||||
```swift
|
||||
let objects = CoreStore.fetchAll(
|
||||
From(MyEntity),
|
||||
Where("entityID", isEqualTo: 1),
|
||||
OrderBy(.Ascending("entityID"), .Descending("name")),
|
||||
let people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where("age > 30"),
|
||||
OrderBy(.Ascending("name"), .Descending("age")),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
fetchRequest.includesPendingChanges = true
|
||||
fetchRequest.includesPendingChanges = false
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Simple queries:
|
||||
```swift
|
||||
let count = CoreStore.queryValue(
|
||||
From(MyEntity),
|
||||
Select<Int>(.Count("entityID"))
|
||||
let maxAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.Maximum("age"))
|
||||
)
|
||||
```
|
||||
|
||||
Check out the **CoreStoreDemo** app as well!
|
||||
|
||||
## Quick jumps
|
||||
|
||||
## Contents
|
||||
|
||||
- [Architecture](#architecture)
|
||||
- [Setting up](#setup)
|
||||
@@ -72,11 +78,10 @@ let count = CoreStore.queryValue(
|
||||
- [Fetching and querying](#fetch_query)
|
||||
- [Logging and error handling](#logging)
|
||||
- [Observing changes and notifications](#observing)
|
||||
- [Importing data](#importing)
|
||||
|
||||
|
||||
|
||||
## <a name="architecture">Architecture</a>
|
||||
## <a id="architecture"></a>Architecture
|
||||
For maximum safety and performance, CoreStore will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of CoreStore before you use it in your apps.
|
||||
|
||||
If you are already familiar with the inner workings of CoreData, here is a mapping of `CoreStore` abstractions:
|
||||
@@ -91,7 +96,7 @@ Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalReco
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/6734049/40579660-ce99-11e4-9d38-829877386afb.png" alt="nested contexts" height=271 />
|
||||
|
||||
This ensures maximum data integrity between contexts without blocking the main queue. But as <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">Florian Kugler's investigation</a> found out, merging contexts is still by far faster than saving nested contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions*:
|
||||
Nesting context saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But as <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">Florian Kugler's investigation</a> found out, merging contexts is still by far faster than saving nested contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/6734050/4078b642-ce99-11e4-95ea-c0c1d24fbe80.png" alt="nested contexts and merge hybrid" height=212 />
|
||||
|
||||
@@ -99,10 +104,10 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
|
||||
|
||||
|
||||
|
||||
## <a name="setup">Setting up</a>
|
||||
## <a id="setup"></a>Setting up
|
||||
The simplest way to initialize CoreStore is to add a default store to the default stack:
|
||||
```swift
|
||||
CoreStore.defaultStack.addSQLiteStore()
|
||||
CoreStore.addSQLiteStore()
|
||||
```
|
||||
This one-liner does the following:
|
||||
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
|
||||
@@ -151,7 +156,6 @@ class MyViewController: UIViewController {
|
||||
```
|
||||
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
|
||||
```swift
|
||||
|
||||
class MyViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -164,32 +168,609 @@ class MyViewController: UIViewController {
|
||||
}
|
||||
```
|
||||
|
||||
Check out the *CoreStore.swift* and *DataStack.swift files* if you want to explore the inner workings of the data stack.
|
||||
|
||||
|
||||
## <a id="transactions">Saving and processing transactions</a>
|
||||
To ensure deterministic state for objects in the read-only `NSManagedObjectContext`, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn *transactions* from `DataStack` instances:
|
||||
```swift
|
||||
let dataStack = self.dataStack
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
or for the default stack, directly from `CoreStore`:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded.
|
||||
|
||||
## <a name="transactions">Saving and processing transactions</a>
|
||||
(implemented; README pending)
|
||||
The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at you disposal: *asynchronous*, *synchronous*, and *detached*.
|
||||
|
||||
**Asynchronous transactions** are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`.
|
||||
|
||||
**Synchronous transactions** are created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning:
|
||||
```swift
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
// make changes
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
`transaction` above is a `SynchronousDataTransaction` instance.
|
||||
|
||||
Since `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues.
|
||||
|
||||
**Detached transactions** are special in that they do not enclose updates within a closure:
|
||||
```swift
|
||||
let transaction = CoreStore.beginDetached()
|
||||
// make changes
|
||||
downloadJSONWithCompletion({ (json) -> Void in
|
||||
|
||||
// make other changes
|
||||
transaction.commit()
|
||||
})
|
||||
downloadAnotherJSONWithCompletion({ (json) -> Void in
|
||||
|
||||
// make some other changes
|
||||
transaction.commit()
|
||||
})
|
||||
```
|
||||
This allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, "with great power comes great race conditions."
|
||||
|
||||
As the above example also shows, only detached transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert.
|
||||
|
||||
|
||||
You've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below.
|
||||
|
||||
## <a name="fetch_query">Fetching and querying</a>
|
||||
(implemented; README pending)
|
||||
### Creating objects
|
||||
The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create:
|
||||
```swift
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
```
|
||||
While the syntax is straightforward, CoreStore does not just naively insert a new object. This single line does the following:
|
||||
- Checks that the entity type exists in any of the transaction's parent persistent store
|
||||
- If the entity belongs to only one persistent store, a new object is inserted into that store and returned from `create(...)`
|
||||
- If the entity does not belong to any store, an assert will be triggered. **This is a programmer error and should never occur in production code.**
|
||||
- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time where it makes sense (not during save).
|
||||
|
||||
If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Config1"))
|
||||
|
||||
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>(nil))
|
||||
|
||||
Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other store the entity belongs to.
|
||||
|
||||
### Updating objects
|
||||
|
||||
After creating an object from the transaction, you can simply update its properties as normal:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
person.name = "John Smith"
|
||||
person.age = 30
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
To update an existing object, fetch the object's instance from the transaction:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let person = transaction.fetchOne(
|
||||
From(MyPersonEntity),
|
||||
Where("name", isEqualTo: "Jane Smith")
|
||||
)
|
||||
person.age = person.age + 1
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
*(For more about fetching, read [Fetching and querying](#fetch_query))*
|
||||
|
||||
**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object:
|
||||
```swift
|
||||
let jane: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// WRONG: jane.age = jane.age + 1
|
||||
// RIGHT:
|
||||
let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance
|
||||
jane.age = jane.age + 1
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
This is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction:
|
||||
```swift
|
||||
let jane: MyPersonEntity = // ...
|
||||
let john: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
// WRONG: jane.friends = [john]
|
||||
// RIGHT:
|
||||
let jane = transaction.edit(jane)
|
||||
let john = transaction.edit(john)
|
||||
jane.friends = [john]
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
### Deleting objects
|
||||
|
||||
Deleting an object is simpler as you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you):
|
||||
```swift
|
||||
let john: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.delete(john)
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
or several objects at once:
|
||||
```swift
|
||||
let john: MyPersonEntity = // ...
|
||||
let jane: MyPersonEntity = // ...
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.delete(john, jane)
|
||||
// transaction.delete([john, jane]) is also allowed
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
If you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to:
|
||||
```swift
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
transaction.deleteAll(
|
||||
From(MyPersonEntity)
|
||||
Where("age > 30")
|
||||
)
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
## <a id="fetch_query"></a>Fetching and querying
|
||||
Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*:
|
||||
- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when:
|
||||
- results need to be `NSManagedObject` instances
|
||||
- unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)
|
||||
- A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when:
|
||||
- you need to compute aggregate functions (see below for a list of supported functions)
|
||||
- results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc.
|
||||
- only specific attribute keys need to be included in the results
|
||||
- unsaved objects should be ignored
|
||||
|
||||
The search conditions for fetches and queries are specified using *clauses*. All fetches and queries require a `From` clause that indicates the target entity type:
|
||||
```swift
|
||||
let people = CoreStore.fetchAll(From(MyPersonEntity))
|
||||
// CoreStore.fetchAll(From<MyPersonEntity>()) works as well
|
||||
```
|
||||
`people` in the example above will be of type `[MyPersonEntity]`. The `From(MyPersonEntity)` clause indicates a fetch to all persistent stores that `MyPersonEntity` belong to.
|
||||
|
||||
If the entity exists in multiple configurations and you need to only search from a particular configuration, provide the `From` clause the configuration name for the destination persistent store:
|
||||
```swift
|
||||
let people = CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
|
||||
```
|
||||
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
|
||||
```swift
|
||||
let person = CoreStore.fetchAll(From<MyPersonEntity>(nil))
|
||||
```
|
||||
Now we know how to use a `From` clause, let's move on to fetching and querying.
|
||||
|
||||
#### Fetching
|
||||
|
||||
There are currently 5 fetch methods you can call from `CoreStore`, from a `DataStack` instance, or from a `BaseDataTransaction` instance. All of the methods below accept the same parameters: a required `From` clause, and an optional series of `Where`, `OrderBy`, and/or `Tweak` clauses.
|
||||
|
||||
- `fetchAll(_:_:)` - returns an array of all objects that match the criteria.
|
||||
- `fetchOne(_:_:)` - returns the first object that match the criteria.
|
||||
- `fetchCount(_:_:)` - returns the number of objects that match the criteria.
|
||||
- `fetchObjectIDs(_:_:)`` - returns an array of `NSManagedObjectID`s for all objects that match the criteria.
|
||||
- `fetchObjectID(_:_:)` - returns the `NSManagedObjectID`s for the first objects that match the criteria.
|
||||
|
||||
Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch.
|
||||
|
||||
**`Where` clause**
|
||||
|
||||
The `Where` clause is CoreStore's `NSPredicate` wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that `NSPredicate` does (except for `-predicateWithBlock:`, which Core Data does not support):
|
||||
```swift
|
||||
var people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where("%K > %d", "age", 30) // string format initializer
|
||||
)
|
||||
people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where(true) // boolean initializer
|
||||
)
|
||||
```
|
||||
If you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well:
|
||||
```swift
|
||||
let predicate = NSPredicate(...)
|
||||
var people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where(predicate) // predicate initializer
|
||||
)
|
||||
```
|
||||
`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings in the conditions:
|
||||
```swift
|
||||
var people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where("age > %d", 30) && Where("gender == %@", "M")
|
||||
)
|
||||
```
|
||||
If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned.
|
||||
|
||||
**`OrderBy` clause**
|
||||
|
||||
The `OrderBy` clause is CoreStore's `NSSortDescriptor` wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with.
|
||||
```swift
|
||||
var mostValuablePeople = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Descending("rating"), .Ascending("surname"))
|
||||
)
|
||||
```
|
||||
As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. The associated value for the `SortKey` enumeration is the attribute key string.
|
||||
|
||||
You can use the `+` and `+=` operator to append `OrderBy`s together. This is useful when sorting conditionally:
|
||||
```swift
|
||||
var orderBy = OrderBy(.Descending("rating"))
|
||||
if sortFromYoungest {
|
||||
orderBy += OrderBy(.Ascending("age"))
|
||||
}
|
||||
var mostValuablePeople = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
orderBy
|
||||
)
|
||||
```
|
||||
|
||||
**`Tweak` clause**
|
||||
|
||||
The `Tweak` clause lets you, well, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:
|
||||
```swift
|
||||
var people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
Where("age > %d", 30),
|
||||
OrderBy(.Ascending("surname")),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
fetchRequest.includesPendingChanges = false
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.includesSubentities = false
|
||||
}
|
||||
)
|
||||
```
|
||||
The clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause.
|
||||
`Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions.
|
||||
|
||||
Do note that while `Tweak` lets you micro-configure its `NSFetchRequest`, don't forget that CoreStore already preconfigured that `NSFetchRequest` to suitable defaults. Only use `Tweak` when you know what you are doing!
|
||||
|
||||
#### Querying
|
||||
One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with `NSDictionaryResultType` and `-[NSFetchedRequest propertiesToFetch]`, you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes querying easy by exposing the 2 methods below:
|
||||
|
||||
- `queryValue(_:_:_:)` - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, `queryValue(...)` only returns the first item.
|
||||
- `queryAttributes(_:_:_:)` - returns an array of dictionaries containing attribute keys with their corresponding values.
|
||||
|
||||
Both methods above accept the same parameters: a required `From` clause, a required `Select<T>` clause, and an optional series of `Where`, `OrderBy`, `GroupBy`, and/or `Tweak` clauses.
|
||||
|
||||
Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how you would when fetching. For querying, you also need to know how to use the `Select<T>` and `GroupBy` clauses.
|
||||
|
||||
**`Select<T>` clause**
|
||||
|
||||
The `Select<T>` clause specifies the target attribute/aggregate key and the return type:
|
||||
```swift
|
||||
let johnsAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>("age"),
|
||||
Where("name == %@", "John Smith")
|
||||
)
|
||||
```
|
||||
The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select<Int>` generic type. For `queryValue(...)`, the following are allowed as the return type (and as the generic type for `Select<T>`):
|
||||
- `Bool`
|
||||
- `Int8`
|
||||
- `Int16`
|
||||
- `Int32`
|
||||
- `Int64`
|
||||
- `Double`
|
||||
- `Float`
|
||||
- `String`
|
||||
- `NSNumber`
|
||||
- `NSString`
|
||||
- `NSDecimalNumber`
|
||||
- `NSDate`
|
||||
- `NSData`
|
||||
- `NSManagedObjectID`
|
||||
- `NSString`
|
||||
|
||||
For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed omit the generic type:
|
||||
```swift
|
||||
let allAges = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("age")
|
||||
)
|
||||
```
|
||||
|
||||
If you only need a value for a particular attribute, you can just specify the key name (like we did with `Select<Int>("age")`), but several aggregate functions can also be used as parameter to `Select`:
|
||||
- `.Average(...)`
|
||||
- `.Count(...)`
|
||||
- `.Maximum(...)`
|
||||
- `.Minimum(...)`
|
||||
- `.Sum(...)`
|
||||
|
||||
```swift
|
||||
let oldestAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.Maximum("age"))
|
||||
)
|
||||
```
|
||||
|
||||
For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", "age")
|
||||
)
|
||||
```
|
||||
`personJSON` will then have the value:
|
||||
```json
|
||||
[
|
||||
[
|
||||
"name": "John Smith",
|
||||
"age": 30
|
||||
],
|
||||
[
|
||||
"name": "Jane Doe",
|
||||
"age": 22
|
||||
]
|
||||
]
|
||||
```
|
||||
You can also include an aggregate as well:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", .Count("friends"))
|
||||
)
|
||||
```
|
||||
which returns:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"name": "John Smith",
|
||||
"count(friends)": 42
|
||||
],
|
||||
[
|
||||
"name": "Jane Doe",
|
||||
"count(friends)": 231
|
||||
]
|
||||
]
|
||||
```
|
||||
The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", .Count("friends", As: "friendsCount"))
|
||||
)
|
||||
```
|
||||
which now returns:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"name": "John Smith",
|
||||
"friendsCount": 42
|
||||
],
|
||||
[
|
||||
"name": "Jane Doe",
|
||||
"friendsCount": 231
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
**`GroupBy` clause**
|
||||
|
||||
The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value.
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("age", .Count("age", As: "count")),
|
||||
GroupBy("age")
|
||||
)
|
||||
```
|
||||
this returns dictionaries that shows the count for each `"age"`:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"age": 42,
|
||||
"count": 1
|
||||
],
|
||||
[
|
||||
"age": 22,
|
||||
"count": 1
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
## <a id="logging"></a>Logging and error handling
|
||||
One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.
|
||||
```swift
|
||||
final class MyLogger: CoreStoreLogger {
|
||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
|
||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
}
|
||||
```
|
||||
Then pass an instance of this class to `CoreStore`:
|
||||
```swift
|
||||
CoreStore.logger = MyLogger()
|
||||
```
|
||||
Doing so channels all logging calls to your logger.
|
||||
|
||||
Note that to keep the call stack information intact, all calls to these methods are not thread-managed. Thus you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.
|
||||
|
||||
## <a id="observing"></a>Observing changes and notifications
|
||||
CoreStore provides type-safe wrappers for observing managed objects:
|
||||
|
||||
- `ManagedObjectController`: use to observe changes to a single `NSManagedObject` instance (instead of Key-Value Observing)
|
||||
- `ManagedObjectListController`: use to observe changes to a list of `NSManagedObject` instances (instead of `NSFetchedResultsController`)
|
||||
|
||||
#### Observe a single object
|
||||
|
||||
To observe an object, implement the `ManagedObjectObserver` protocol and specify the `EntityType`:
|
||||
```swift
|
||||
class MyViewController: UIViewController, ManagedObjectObserver {
|
||||
func managedObjectWillUpdate(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
||||
// ...
|
||||
}
|
||||
|
||||
func managedObjectWasUpdated(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) {
|
||||
// ...
|
||||
}
|
||||
|
||||
func managedObjectWasDeleted(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
We then need to keep a `ManagedObjectController` instance and register our `ManagedObjectObserver` as an observer:
|
||||
```swift
|
||||
let person: MyPersonEntity = // ...
|
||||
self.objectController = CoreStore.observeObject(person)
|
||||
self.objectController.addObserver(self)
|
||||
```
|
||||
The controller will then notify our observer whenever the object's attributes change. You can add multiple `ManagedObjectObserver`s to a single `ManagedObjectController` without any problem. This means you can just share around the `ManagedObjectController` instance to different screens without problem.
|
||||
|
||||
You can get `ManagedObjectController`'s object through its `object` property. If the object is deleted, the `object` property will become `nil` to prevent further access.
|
||||
|
||||
While `ManagedObjectController` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers.
|
||||
|
||||
#### Observe a list of objects
|
||||
To observe a list of objects, implement one of the `ManagedObjectListChangeObserver` protocols and specify the `EntityType`:
|
||||
```swift
|
||||
class MyViewController: UIViewController, ManagedObjectListChangeObserver {
|
||||
func managedObjectListWillChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
||||
// ...
|
||||
}
|
||||
|
||||
func managedObjectListDidChange(listController: ManagedObjectListController<MyPersonEntity>) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
Including `ManagedObjectListChangeObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification:
|
||||
- `ManagedObjectListChangeObserver`: lets you handle these callback methods:
|
||||
- `func managedObjectListWillChange(listController: ManagedObjectListController<T>)`
|
||||
- `func managedObjectListDidChange(listController: ManagedObjectListController<T>)`
|
||||
- `ManagedObjectListObjectObserver`: in addition to `ManagedObjectListChangeObserver` methods, also lets you handle object inserts, updates, and deletes:
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertObject object: T, toIndexPath indexPath: NSIndexPath)`
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteObject object: T, fromIndexPath indexPath: NSIndexPath)`
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didUpdateObject object: T, atIndexPath indexPath: NSIndexPath)`
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didMoveObject object: T, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)`
|
||||
- `ManagedObjectListSectionObserver`: in addition to `ManagedObjectListObjectObserver` methods, also lets you handle section inserts and deletes:
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)`
|
||||
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)`
|
||||
|
||||
We then need to create a `ManagedObjectListController` instance and register our `ManagedObjectListChangeObserver` as an observer:
|
||||
```swift
|
||||
self.listController = CoreStore.observeObjectList(
|
||||
From(MyPersonEntity),
|
||||
Where("age > 30"),
|
||||
OrderBy(.Ascending("name")),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
}
|
||||
)
|
||||
self.listController.addObserver(self)
|
||||
```
|
||||
Similar to `ManagedObjectController`, a `ManagedObjectListController` can also have multiple `ManagedObjectListChangeObserver`s registered to a single `ManagedObjectListController`.
|
||||
|
||||
If you have noticed, the `observeObjectList(...)` method accepts `Where`, `OrderBy`, and `Tweak` clauses exactly like a fetch. As the list maintained by `ManagedObjectListController` needs to have a deterministic order, at least the `From` and `OrderBy` clauses are required.
|
||||
|
||||
A `ManagedObjectListController` created from `observeObjectList(...)` will maintain a single-section list. You can therefore access its contents with just an index:
|
||||
```swift
|
||||
let firstPerson = self.listController[0]
|
||||
```
|
||||
|
||||
If the list needs to be grouped into sections, create the `ManagedObjectListController` instance with the `observeSectionedList(...)` method and a `SectionedBy` clause:
|
||||
```swift
|
||||
self.listController = CoreStore.observeSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionedBy("age"),
|
||||
Where("gender", isEqualTo: "M"),
|
||||
OrderBy(.Ascending("age"), .Ascending("name")),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
}
|
||||
)
|
||||
```
|
||||
A list controller created this way will group the objects by the attribute key indicated by the `SectionedBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionedBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.)
|
||||
|
||||
The `SectionedBy` clause can also be passed a closure to transform the section name into a displayable string:
|
||||
```swift
|
||||
self.listController = CoreStore.observeSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionedBy("age") { (sectionName) -> String? in
|
||||
"\(sectionName) years old"
|
||||
},
|
||||
OrderBy(.Ascending("age"), .Ascending("name"))
|
||||
)
|
||||
```
|
||||
This is useful when implementing a `UITableViewDelegate`'s section header:
|
||||
```swift
|
||||
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
let sectionInfo = self.listController.sectionInfoAtIndex(section)
|
||||
// sectionInfo is an NSFetchedResultsSectionInfo instance
|
||||
return sectionInfo.name
|
||||
}
|
||||
```
|
||||
|
||||
To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
|
||||
```swift
|
||||
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
|
||||
let person1 = self.listController[indexPath]
|
||||
let person2 = self.listController[1, 2]
|
||||
// person1 and person2 are the same object
|
||||
```
|
||||
|
||||
# TODO
|
||||
- Data importing utilities for transactions
|
||||
- Migration utilities
|
||||
- Support iCloud stores
|
||||
|
||||
|
||||
# Installation
|
||||
- Requires iOS 8 SDK and above
|
||||
- Swift 1.2
|
||||
|
||||
## <a name="logging">Logging and error handling</a>
|
||||
(implemented; README pending)
|
||||
### Install with Cocoapods
|
||||
```
|
||||
pod 'CoreStore'
|
||||
```
|
||||
This installs CoreStore as a framework. Declare `import CoreStore` in your swift file to use the library.
|
||||
|
||||
### Install as Git Submodule
|
||||
```
|
||||
git submodule add https://github.com/JohnEstropia/CoreStore.git <destination directory>
|
||||
```
|
||||
#### To install as a framework:
|
||||
Drag and drop **CoreStore.xcodeproj** to your project.
|
||||
#### To include directly in your app module:
|
||||
Add all *.swift* files to your project.
|
||||
|
||||
# Contributions
|
||||
While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.
|
||||
Please feel free to report any issues, suggestions, or criticisms!
|
||||
日本語で連絡していただいても構いません!
|
||||
|
||||
## <a name="observing">Observing changes and notifications</a>
|
||||
(implemented; README pending)
|
||||
|
||||
|
||||
|
||||
## <a name="importing">Importing data</a>
|
||||
(currently implementing)
|
||||
|
||||
|
||||
## License
|
||||
CoreStore is released under an MIT license. See the LICENSE file for more information
|
||||
|
||||
|
||||
Reference in New Issue
Block a user