added ability to query aggregates and attributes straight from the persistent store

This commit is contained in:
John Rommel Estropia
2015-03-18 01:25:58 +09:00
parent 13ca911d77
commit bf41605da9
28 changed files with 1311 additions and 488 deletions

View File

@@ -1,99 +0,0 @@
//
// AggregateFunction.swift
// HardcoreData
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - AggregateFunction
public enum AggregateFunction {
// MARK: Public
case Average(AttributeName)
case Sum(AttributeName)
case Count(AttributeName)
case Minimum(AttributeName)
case Maximum(AttributeName)
case Medium(AttributeName)
case Mode(AttributeName)
case StandardDeviation(AttributeName)
// MARK: Internal
internal func createExpression() -> NSExpression {
switch self {
case .Average(let attributeName):
return NSExpression(
forFunction: "average:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Sum(let attributeName):
return NSExpression(
forFunction: "sum:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Count(let attributeName):
return NSExpression(
forFunction: "count:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Minimum(let attributeName):
return NSExpression(
forFunction: "min:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Maximum(let attributeName):
return NSExpression(
forFunction: "max:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Medium(let attributeName):
return NSExpression(
forFunction: "medium:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .Mode(let attributeName):
return NSExpression(
forFunction: "mode:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
case .StandardDeviation(let attributeName):
return NSExpression(
forFunction: "stddev:",
arguments: [NSExpression(forKeyPath: "\(attributeName)")]
)
}
}
}

View File

@@ -1,232 +0,0 @@
//
// AggregateResultType.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - AggregateResultType
public protocol AggregateResultType {
static var attributeType: NSAttributeType { get }
static func fromResultObject(result: AnyObject) -> Self
}
extension Bool: AggregateResultType {
public static var attributeType: NSAttributeType {
return .BooleanAttributeType
}
public static func fromResultObject(result: AnyObject) -> Bool {
return (result as! NSNumber).boolValue
}
}
extension Int8: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int8 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension Int16: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int16 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension Int32: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int32 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension Int64: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int64 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension Int: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension UInt8: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> UInt8 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension UInt16: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> UInt16 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension UInt32: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> UInt32 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension UInt64: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> UInt64 {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension UInt: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> UInt {
return numericCast((result as! NSNumber).longLongValue)
}
}
extension Double: AggregateResultType {
public static var attributeType: NSAttributeType {
return .DoubleAttributeType
}
public static func fromResultObject(result: AnyObject) -> Double {
return (result as! NSNumber).doubleValue
}
}
extension Float: AggregateResultType {
public static var attributeType: NSAttributeType {
return .FloatAttributeType
}
public static func fromResultObject(result: AnyObject) -> Float {
return (result as! NSNumber).floatValue
}
}
extension NSNumber: AggregateResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public class func fromResultObject(result: AnyObject) -> Self {
return self(bytes: result.bytes, objCType: result.objCType)
}
}
extension NSDate: AggregateResultType {
public static var attributeType: NSAttributeType {
return .DateAttributeType
}
public class func fromResultObject(result: AnyObject) -> Self {
return self(timeInterval: 0.0, sinceDate: result as! NSDate)
}
}

View File

@@ -41,7 +41,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
*/
public func commit(completion: (result: SaveResult) -> Void) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.")
self.isCommitted = true
@@ -62,7 +62,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
*/
public func commitAndWait() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.")
self.isCommitted = true
@@ -77,6 +77,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
*/
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a <\(self.dynamicType)> outside its designated queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed <\(self.dynamicType)>.")
return SynchronousDataTransaction(

View File

@@ -35,63 +35,113 @@ public extension BaseDataTransaction {
public func fetchOne<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> T? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchOne(entity, queryClauses)
}
public func fetchOne<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> T? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchOne(entity, queryClauses)
}
public func fetchAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchAll(entity, queryClauses)
}
public func fetchAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchAll(entity, queryClauses)
}
public func fetchCount<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchCount(entity, queryClauses)
}
public func fetchCount<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchCount(entity, queryClauses)
}
public func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchObjectID(entity, queryClauses)
}
public func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchObjectID(entity, queryClauses)
}
public func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchObjectIDs(entity, queryClauses)
}
public func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.")
return self.context.fetchObjectIDs(entity, queryClauses)
}
public func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside its designated queue.")
return self.context.deleteAll(entity, queryClauses)
}
public func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside its designated queue.")
return self.context.deleteAll(entity, queryClauses)
}
public func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? {
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: FetchClause...) -> U? {
let result = self.context.queryAggregate(entity, function: function, queryClauses)
return result
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.")
return self.context.queryValue(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? {
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: [FetchClause]) -> U? {
let result = self.context.queryAggregate(entity, function: function, queryClauses)
return result
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.")
return self.context.queryValue(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? {
public func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.context.queryAggregate(entity, function: function, queryClauses)
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.")
return self.context.queryAttributes(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? {
public func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
return self.context.queryAggregate(entity, function: function, queryClauses)
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.")
return self.context.queryAttributes(entity, selectClause, queryClauses)
}
}

View File

@@ -50,7 +50,7 @@ public /*abstract*/ class BaseDataTransaction {
*/
public func create<T: NSManagedObject>(entity: T.Type) -> T {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(entity)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(entity)> outside its designated queue.")
return T.createInContext(self.context)
}
@@ -63,7 +63,7 @@ public /*abstract*/ class BaseDataTransaction {
*/
public func fetch<T: NSManagedObject>(object: T) -> T? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(object.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(object.dynamicType)> outside its designated queue.")
return object.inContext(self.context)
}
@@ -75,7 +75,7 @@ public /*abstract*/ class BaseDataTransaction {
*/
public func delete(object: NSManagedObject) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type <\(object.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type <\(object.dynamicType)> outside its designated queue.")
object.deleteFromContext()
}
@@ -87,7 +87,7 @@ public /*abstract*/ class BaseDataTransaction {
*/
public func rollback() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a <\(self.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a <\(self.dynamicType)> outside its designated queue.")
self.context.reset()
}

View File

@@ -1,5 +1,5 @@
//
// CustomizeQuery.swift
// CustomizeFetch.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
@@ -27,9 +27,9 @@ import Foundation
import CoreData
// MARK: - CustomizeQuery
// MARK: - CustomizeFetch
public struct CustomizeQuery: FetchClause {
public struct CustomizeFetch: FetchClause {
// MARK: Public

View File

@@ -0,0 +1,50 @@
//
// DataStack+Observing.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
import GCDKit
// MARK: - DataStack
public extension DataStack {
// MARK: Public
public func observeObjectList<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> ManagedObjectListController<T> {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
// TODO: sectionNameKeyPath and cacheResults
return ManagedObjectListController(
dataStack: self,
entity: entity,
sectionNameKeyPath: nil,
cacheResults: false,
queryClauses: queryClauses
)
}
}

View File

@@ -25,6 +25,7 @@
import Foundation
import CoreData
import GCDKit
// MARK: - DataStack
@@ -35,63 +36,113 @@ public extension DataStack {
public func fetchOne<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> T? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchOne(entity, queryClauses)
}
public func fetchOne<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> T? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchOne(entity, queryClauses)
}
public func fetchAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchAll(entity, queryClauses)
}
public func fetchAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchAll(entity, queryClauses)
}
public func fetchCount<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchCount(entity, queryClauses)
}
public func fetchCount<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchCount(entity, queryClauses)
}
public func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchObjectID(entity, queryClauses)
}
public func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchObjectID(entity, queryClauses)
}
public func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchObjectIDs(entity, queryClauses)
}
public func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.fetchObjectIDs(entity, queryClauses)
}
public func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.deleteAll(entity, queryClauses)
}
public func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.deleteAll(entity, queryClauses)
}
public func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? {
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: FetchClause...) -> U? {
let result = self.mainContext.queryAggregate(entity, function: function, queryClauses)
return result
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.queryValue(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? {
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: [FetchClause]) -> U? {
let result = self.mainContext.queryAggregate(entity, function: function, queryClauses)
return result
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.queryValue(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? {
public func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.mainContext.queryAggregate(entity, function: function, queryClauses)
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.queryAttributes(entity, selectClause, queryClauses)
}
public func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? {
public func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
return self.mainContext.queryAggregate(entity, function: function, queryClauses)
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.")
return self.mainContext.queryAttributes(entity, selectClause, queryClauses)
}
}

View File

@@ -25,6 +25,7 @@
import Foundation
import CoreData
import GCDKit
// MARK: - DataStack
@@ -40,6 +41,8 @@ public extension DataStack {
*/
public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.")
AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
@@ -54,6 +57,8 @@ public extension DataStack {
*/
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.")
return SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue,
@@ -67,6 +72,8 @@ public extension DataStack {
*/
public func beginDetached() -> DetachedDataTransaction {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.")
return DetachedDataTransaction(
mainContext: self.rootSavingContext,
queue: .Main)

View File

@@ -105,7 +105,7 @@ public final class DataStack {
var error: NSError?
var store: NSPersistentStore?
coordinator.performSynchronously {
coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
@@ -203,7 +203,7 @@ public final class DataStack {
var store: NSPersistentStore?
var persistentStoreError: NSError?
coordinator.performSynchronously {
coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
@@ -238,7 +238,7 @@ public final class DataStack {
error: nil)
var store: NSPersistentStore?
coordinator.performSynchronously {
coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,

View File

@@ -41,7 +41,7 @@ public final class DetachedDataTransaction: BaseDataTransaction {
*/
public func commit(completion: (result: SaveResult) -> Void) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.")
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in

View File

@@ -29,7 +29,4 @@ import CoreData
// MARK: - FetchClause
public protocol FetchClause {
func applyToFetchRequest(fetchRequest: NSFetchRequest)
}
public protocol FetchClause: QueryClause { }

View File

@@ -0,0 +1,80 @@
//
// GroupBy.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - GroupBy
public struct GroupBy: QueryClause {
// MARK: Public
public init(_ keyPaths: [KeyPath]) {
self.keyPaths = keyPaths
}
public init() {
self.init([])
}
public init(_ keyPath: KeyPath, _ subKeyPaths: KeyPath...) {
self.init([keyPath] + subKeyPaths)
}
public let keyPaths: [KeyPath]
// MARK: QueryClause
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if fetchRequest.propertiesToGroupBy != nil {
HardcoreData.log(.Warning, message: "An existing \"propertiesToGroupBy\" for the <\(NSFetchRequest.self)> was overwritten by <\(self.dynamicType)> query clause.")
}
fetchRequest.propertiesToGroupBy = self.keyPaths
// let entityDescription = fetchRequest.entity!
// let propertyMapping = entityDescription.propertiesByName
// fetchRequest.propertiesToGroupBy = self.keyPaths.reduce([AnyObject]()) { (var properties, keyPath) -> [AnyObject] in
//
// if let propertyDescription: AnyObject = propertyMapping[keyPath] {
//
// properties.append(propertyDescription)
// }
// else {
//
// HardcoreData.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.")
// }
//
// return properties
// }
}
}

View File

@@ -61,6 +61,26 @@ public extension HardcoreData {
return self.defaultStack.fetchCount(entity, queryClauses)
}
public static func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? {
return self.defaultStack.fetchObjectID(entity, queryClauses)
}
public static func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? {
return self.defaultStack.fetchObjectID(entity, queryClauses)
}
public static func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? {
return self.defaultStack.fetchObjectIDs(entity, queryClauses)
}
public static func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? {
return self.defaultStack.fetchObjectIDs(entity, queryClauses)
}
public static func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
return self.defaultStack.deleteAll(entity, queryClauses)
@@ -71,25 +91,23 @@ public extension HardcoreData {
return self.defaultStack.deleteAll(entity, queryClauses)
}
public static func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? {
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: FetchClause...) -> U? {
let result = self.defaultStack.queryAggregate(entity, function: function, queryClauses)
return result
return self.defaultStack.queryValue(entity, selectClause, queryClauses)
}
public static func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? {
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: [FetchClause]) -> U? {
let result = self.defaultStack.queryAggregate(entity, function: function, queryClauses)
return result
return self.defaultStack.queryValue(entity, selectClause, queryClauses)
}
public static func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? {
public static func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.defaultStack.queryAggregate(entity, function: function, queryClauses)
return self.defaultStack.queryAttributes(entity, selectClause, queryClauses)
}
public static func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? {
public static func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
return self.defaultStack.queryAggregate(entity, function: function, queryClauses)
return self.defaultStack.queryAttributes(entity, selectClause, queryClauses)
}
}

View File

@@ -0,0 +1,27 @@
//
// ManagedObjectController.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData

View File

@@ -0,0 +1,125 @@
//
// ManagedObjectListController.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
import GCDKit
// MARK: - ManagedObjectListController
public final class ManagedObjectListController<T: NSManagedObject>: NSFetchedResultsControllerDelegate {
// MARK: Public
public typealias EntityType = T
public func addObserver<U: ManagedObjectListObserver where U.EntityType == EntityType>(observer: U) {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a <\(U.self)> outside the main queue.")
self.observers.addObject(observer)
}
public func removeObserver<U: ManagedObjectListObserver where U.EntityType == EntityType>(observer: U) {
HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a <\(U.self)> outside the main queue.")
self.observers.removeObject(observer)
}
// MARK: NSFetchedResultsControllerDelegate
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
}
private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
}
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
}
private func controllerDidChangeContent(controller: NSFetchedResultsController) {
}
private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
return nil
}
// MARK: Internal
internal init(dataStack: DataStack, entity: T.Type, sectionNameKeyPath: KeyPath?, cacheResults: Bool, queryClauses: [FetchClause]) {
let context = dataStack.mainContext
let fetchRequest = NSFetchRequest()
fetchRequest.entity = context.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
for clause in queryClauses {
clause.applyToFetchRequest(fetchRequest)
}
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: sectionNameKeyPath,
cacheName: (cacheResults
? "\(ManagedObjectListController<T>.self).\(NSUUID())"
: nil)
)
self.fetchedResultsController = fetchedResultsController
self.parentStack = dataStack
self.observers = NSHashTable.weakObjectsHashTable()
fetchedResultsController.delegate = self
var error: NSError?
if !fetchedResultsController.performFetch(&error) {
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed to perform fetch on \(NSFetchedResultsController.self)")
}
}
// MARK: Private
private let fetchedResultsController: NSFetchedResultsController
private weak var parentStack: DataStack?
private let observers: NSHashTable
}

View File

@@ -0,0 +1,80 @@
//
// ManagedObjectListObserver.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - ManagedObjectListObserver
public protocol ManagedObjectListObserver: class {
typealias EntityType: NSManagedObject
func managedObjectListWillChange(listController: ManagedObjectListController<EntityType>)
// func managedObjectList(listController: ManagedObjectListController<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
//
// func managedObjectList(listController: ManagedObjectListController<EntityType>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int)
//
// func managedObjectList(listController: ManagedObjectListController<EntityType>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int)
//
// func managedObjectList(listController: ManagedObjectListController<EntityType>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int)
func managedObjectList(listController: ManagedObjectListController<EntityType>, didInsertObject object: EntityType, toItemIndex itemIndex: Int)
func managedObjectList(listController: ManagedObjectListController<EntityType>, didDeleteObject object: EntityType, fromItemIndex itemIndex: Int)
func managedObjectList(listController: ManagedObjectListController<EntityType>, didUpdateObject object: EntityType, atItemIndex itemIndex: Int)
func managedObjectList(listController: ManagedObjectListController<EntityType>, didMoveObject object: EntityType, fromItemIndex: Int, toItemIndex: Int)
func managedObjectListDidChange(listController: ManagedObjectListController<EntityType>)
// private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
//
// }
//
// private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
//
// }
//
// private func controllerWillChangeContent(controller: NSFetchedResultsController) {
//
// }
//
// private func controllerDidChangeContent(controller: NSFetchedResultsController) {
//
// }
//
// private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
//
// return nil
// }
}

View File

@@ -0,0 +1,13 @@
//
// ManagedObjectListSectionInfo.swift
// HardcoreData
//
// Created by John Rommel Estropia on 2015/03/11.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
class ManagedObjectListSectionInfo: NSObject {
}

View File

@@ -133,6 +133,74 @@ internal extension NSManagedObjectContext {
return count
}
internal func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? {
return self.fetchObjectID(entity, queryClauses)
}
internal func fetchObjectID<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 1
fetchRequest.resultType = .ManagedObjectIDResultType
for clause in queryClauses {
clause.applyToFetchRequest(fetchRequest)
}
var fetchResults: [NSManagedObjectID]?
var error: NSError?
self.performBlockAndWait {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID]
}
if fetchResults == nil {
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed executing fetch request.")
return nil
}
return fetchResults?.first
}
internal func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? {
return self.fetchObjectIDs(entity, queryClauses)
}
internal func fetchObjectIDs<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectIDResultType
for clause in queryClauses {
clause.applyToFetchRequest(fetchRequest)
}
var fetchResults: [NSManagedObjectID]?
var error: NSError?
self.performBlockAndWait {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID]
}
if fetchResults == nil {
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed executing fetch request.")
return nil
}
return fetchResults
}
internal func deleteAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: FetchClause...) -> Int? {
return self.deleteAll(entity, queryClauses)
@@ -178,38 +246,18 @@ internal extension NSManagedObjectContext {
return numberOfDeletedObjects
}
internal func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? {
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: FetchClause...) -> U? {
return self.queryAggregateImplementation(entity, function: function, queryClauses)
return self.queryValue(entity, selectClause, queryClauses)
}
internal func queryAggregate<T: NSManagedObject>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? {
return self.queryAggregateImplementation(entity, function: function, queryClauses)
}
internal func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? {
return self.queryAggregateImplementation(entity, function: function, queryClauses)
}
internal func queryAggregate<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? {
return self.queryAggregateImplementation(entity, function: function, queryClauses)
}
internal func queryAggregateImplementation<T: NSManagedObject, U: AggregateResultType>(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = "queryAggregate"
expressionDescription.expressionResultType = U.attributeType
expressionDescription.expression = function.createExpression()
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(entity: T.Type, _ selectClause: Select<U>, _ queryClauses: [FetchClause]) -> U? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.resultType = .DictionaryResultType
fetchRequest.propertiesToFetch = [expressionDescription]
fetchRequest.includesPendingChanges = false
fetchRequest.fetchLimit = 0
selectClause.applyToFetchRequest(fetchRequest)
for clause in queryClauses {
@@ -222,19 +270,54 @@ internal extension NSManagedObjectContext {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error)
}
if fetchResults == nil {
if let fetchResults = fetchResults {
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed executing fetch request.")
if let rawResult = fetchResults.first as? NSDictionary,
let rawObject: AnyObject = rawResult[selectClause.keyPathForFirstSelectTerm()] {
return Select<U>.ReturnType.fromResultObject(rawObject)
}
return nil
}
if let result: AnyObject = (fetchResults?.first as! [String: AnyObject])[expressionDescription.name] {
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed executing fetch request.")
return nil
}
internal func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.queryAttributes(entity, selectClause, queryClauses)
}
internal func queryAttributes<T: NSManagedObject>(entity: T.Type, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 0
selectClause.applyToFetchRequest(fetchRequest)
for clause in queryClauses {
return U.fromResultObject(result)
clause.applyToFetchRequest(fetchRequest)
}
var fetchResults: [AnyObject]?
var error: NSError?
self.performBlockAndWait {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error)
}
if let fetchResults = fetchResults {
return Select<NSDictionary>.ReturnType.fromResultObjects(fetchResults)
}
HardcoreData.handleError(
error ?? NSError(hardcoreDataErrorCode: .UnknownError),
"Failed executing fetch request.")
return nil
}
}

View File

@@ -1,8 +1,8 @@
//
// NSPersistentStoreCoordinator+HardcoreData.swift
// QueryClause.swift
// HardcoreData
//
// Copyright (c) 2014 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,23 +27,9 @@ import Foundation
import CoreData
// MARK: - NSPersistentStoreCoordinator
// MARK: - QueryClause
internal extension NSPersistentStoreCoordinator {
public protocol QueryClause {
// MARK: Internal
internal func performSynchronously(closure: () -> Void) {
if self.respondsToSelector("performBlockAndWait:") {
self.performBlockAndWait(closure)
}
else {
self.lock()
autoreleasepool(closure)
self.unlock()
}
}
}
func applyToFetchRequest(fetchRequest: NSFetchRequest)
}

515
HardcoreData/Select.swift Normal file
View File

@@ -0,0 +1,515 @@
//
// Select.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - SelectResultType
public protocol SelectResultType { }
// MARK: - SelectValueResultType
public protocol SelectValueResultType: SelectResultType {
static func fromResultObject(result: AnyObject) -> Self?
}
// MARK: - SelectAttributesResultType
public protocol SelectAttributesResultType: SelectResultType {
static func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]]
}
// MARK: - SelectTerm
public enum SelectTerm: StringLiteralConvertible {
case Attribute(KeyPath)
case Aggregate(function: String, KeyPath, As: String)
public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "average:",
keyPath,
As: alias ?? "average(\(keyPath))"
)
}
public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "count:",
keyPath,
As: alias ?? "count(\(keyPath))"
)
}
public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "max:",
keyPath,
As: alias ?? "max(\(keyPath))"
)
}
public static func Median(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "median:",
keyPath, As:
alias ?? "median(\(keyPath))"
)
}
public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "min:",
keyPath,
As: alias ?? "min(\(keyPath))"
)
}
public static func StandardDeviation(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "stddev:",
keyPath,
As: alias ?? "stddev(\(keyPath))"
)
}
public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return .Aggregate(
function: "sum:",
keyPath,
As: alias ?? "sum(\(keyPath))"
)
}
public init(stringLiteral value: KeyPath) {
self = .Attribute(value)
}
public init(unicodeScalarLiteral value: KeyPath) {
self = .Attribute(value)
}
public init(extendedGraphemeClusterLiteral value: KeyPath) {
self = .Attribute(value)
}
}
// MARK: - Select
public struct Select<T: SelectResultType> {
// MARK: Public
public typealias ReturnType = T
public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) {
self.selectTerms = [selectTerm] + selectTerms
}
// MARK: Internal
internal func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if fetchRequest.propertiesToFetch != nil {
HardcoreData.log(.Warning, message: "An existing \"propertiesToFetch\" for the <\(NSFetchRequest.self)> was overwritten by <\(self.dynamicType)> query clause.")
}
fetchRequest.includesPendingChanges = false
fetchRequest.resultType = .DictionaryResultType
let entityDescription = fetchRequest.entity!
let propertiesByName = entityDescription.propertiesByName
let attributesByName = entityDescription.attributesByName
var propertiesToFetch = [AnyObject]()
for term in self.selectTerms {
switch term {
case .Attribute(let keyPath):
if let propertyDescription = propertiesByName[keyPath] as? NSPropertyDescription {
propertiesToFetch.append(propertyDescription)
}
else {
HardcoreData.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.")
}
case .Aggregate(let function, let keyPath, let alias):
if let attributeDescription = attributesByName[keyPath] as? NSAttributeDescription {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
expressionDescription.expressionResultType = attributeDescription.attributeType
expressionDescription.expression = NSExpression(
forFunction: function,
arguments: [NSExpression(forKeyPath: keyPath)]
)
propertiesToFetch.append(expressionDescription)
}
else {
HardcoreData.log(.Warning, message: "The attribute \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.")
}
}
}
fetchRequest.propertiesToFetch = propertiesToFetch
}
internal func keyPathForFirstSelectTerm() -> KeyPath {
switch self.selectTerms.first! {
case .Attribute(let keyPath):
return keyPath
case .Aggregate(_, _, let alias):
return alias
}
}
// MARK: Private
private let selectTerms: [SelectTerm]
}
// MARK: - Bool: SelectValueResultType
extension Bool: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .BooleanAttributeType
}
public static func fromResultObject(result: AnyObject) -> Bool? {
return (result as? NSNumber)?.boolValue
}
}
// MARK: - Int8: SelectValueResultType
extension Int8: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int8? {
if let value = (result as? NSNumber)?.longLongValue {
return numericCast(value) as Int8
}
return nil
}
}
// MARK: - Int16: SelectValueResultType
extension Int16: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int16? {
if let value = (result as? NSNumber)?.longLongValue {
return numericCast(value) as Int16
}
return nil
}
}
// MARK: - Int32: SelectValueResultType
extension Int32: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int32? {
if let value = (result as? NSNumber)?.longLongValue {
return numericCast(value) as Int32
}
return nil
}
}
// MARK: - Int64: SelectValueResultType
extension Int64: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int64? {
return (result as? NSNumber)?.longLongValue
}
}
// MARK: - Int: SelectValueResultType
extension Int: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public static func fromResultObject(result: AnyObject) -> Int? {
if let value = (result as? NSNumber)?.longLongValue {
return numericCast(value) as Int
}
return nil
}
}
// MARK: - Double : SelectValueResultType
extension Double: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .DoubleAttributeType
}
public static func fromResultObject(result: AnyObject) -> Double? {
return (result as? NSNumber)?.doubleValue
}
}
// MARK: - Float: SelectValueResultType
extension Float: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .FloatAttributeType
}
public static func fromResultObject(result: AnyObject) -> Float? {
return (result as? NSNumber)?.floatValue
}
}
// MARK: - String: SelectValueResultType
extension String: SelectValueResultType {
public static var attributeType: NSAttributeType {
return .StringAttributeType
}
public static func fromResultObject(result: AnyObject) -> String? {
return result as? NSString as? String
}
}
// MARK: - NSNumber: SelectValueResultType
extension NSNumber: SelectValueResultType {
public class var attributeType: NSAttributeType {
return .Integer64AttributeType
}
public class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSNumber>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSString: SelectValueResultType
extension NSString: SelectValueResultType {
public class var attributeType: NSAttributeType {
return .StringAttributeType
}
public class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSString>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSDecimalNumber: SelectValueResultType
extension NSDecimalNumber: SelectValueResultType {
public override class var attributeType: NSAttributeType {
return .DecimalAttributeType
}
public override class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSDecimalNumber>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSDate: SelectValueResultType
extension NSDate: SelectValueResultType {
public class var attributeType: NSAttributeType {
return .DateAttributeType
}
public class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSDate>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSData: SelectValueResultType
extension NSData: SelectValueResultType {
public class var attributeType: NSAttributeType {
return .BinaryDataAttributeType
}
public class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSData>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSManagedObjectID: SelectValueResultType
extension NSManagedObjectID: SelectValueResultType {
public class var attributeType: NSAttributeType {
return .ObjectIDAttributeType
}
public class func fromResultObject(result: AnyObject) -> Self? {
func forceCast<T: NSManagedObjectID>(object: AnyObject) -> T? {
return (object as? T)
}
return forceCast(result)
}
}
// MARK: - NSManagedObjectID: SelectAttributesResultType
extension NSDictionary: SelectAttributesResultType {
// MARK: SelectAttributesResultType
public class func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]] {
return result as! [[NSString: AnyObject]]
}
}

View File

@@ -32,17 +32,17 @@ public func +(left: SortedBy, right: SortedBy) -> SortedBy {
}
// MARK: - AttributeName
// MARK: - KeyPath
public typealias AttributeName = Selector
public typealias KeyPath = String
// MARK: - SortOrder
public enum SortOrder {
case Ascending(AttributeName)
case Descending(AttributeName)
case Ascending(KeyPath)
case Descending(KeyPath)
}
@@ -69,21 +69,19 @@ public struct SortedBy: FetchClause {
public init(_ order: [SortOrder]) {
self.init(order.map { sortOrder -> NSSortDescriptor in
switch sortOrder {
self.init(
order.map { sortOrder -> NSSortDescriptor in
case .Ascending(let attributeName):
return NSSortDescriptor(
key: NSStringFromSelector(attributeName),
ascending: true)
case .Descending(let attributeName):
return NSSortDescriptor(
key: NSStringFromSelector(attributeName),
ascending: false)
switch sortOrder {
case .Ascending(let keyPath):
return NSSortDescriptor(key: keyPath, ascending: true)
case .Descending(let keyPath):
return NSSortDescriptor(key: keyPath, ascending: false)
}
}
})
)
}
public init(_ order: SortOrder, _ subOrder: SortOrder...) {

View File

@@ -20,7 +20,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
*/
public func commitAndWait() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.")
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.")
self.isCommitted = true
@@ -35,6 +35,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
*/
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a <\(self.dynamicType)> outside its designated queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed <\(self.dynamicType)>.")
return SynchronousDataTransaction(

View File

@@ -68,16 +68,16 @@ public struct Where: FetchClause {
self.init(NSPredicate(format: format, argumentArray: args))
}
public init(_ format: String, argumentArray: [AnyObject]?) {
public init(_ format: String, argumentArray: [NSObject]?) {
self.init(NSPredicate(format: format, argumentArray: argumentArray))
}
public init(_ attributeName: AttributeName, isEqualTo value: NSObject?) {
public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
self.init(value == nil
? NSPredicate(format: "\(attributeName) == nil")
: NSPredicate(format: "\(attributeName) == %@", value!))
? NSPredicate(format: "\(keyPath) == nil")
: NSPredicate(format: "\(keyPath) == %@", value!))
}
public let predicate: NSPredicate