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

import Foundation
import CoreData


public class Cd {

    
    /*
     *  -------------------- Settings -----------------------
     */
    
    /** If true, Cadmium will default to using serial transactions. */
    internal static var defaultSerialTransactions: Bool = false
    
    /*
     *  -------------------- Initialization ----------------------
     */
    
    /**
     Initialize the Cadmium engine with the URLs for the managed object model
     and the SQLite store.
    
     
     - returns: The CdFetchRequest object ready to be configured and then fetched.
    */
    public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {
        return CdFetchRequest<T>()
    }
     
     
    /**
        
        CdManagedObjectContext.initializeMasterContexts(coordinator: psc)
    }
    
    /**
     Initialize the Cadmium engine with a SQLite store.  This initializer helps
     wrap up some of the menial tasks of drilling down exact URLs for resources.
     
     - parameter bundleID:       Pass the bundle identifier that contains your
                                 managed object model file.  This is typically
                                 something like com.yourcompany.yourapp, or
                                 com.yourcompany.yourframework.
     
                                 If you pass nil to this parameter it will look
                                 in the main bundle (which will fail if the
                                 object model is inside of another framework.
     - parameter momdName:       The name of the managed object model
     - parameter sqliteFilename: The name of the SQLite file you are storing data
                                 in.  The initializer will append this filename
                                 to the user's document directory.
                                 If nil, Cadmium will use an in-memory store.
     - parameter serialTX:       If Cadmium should use serial transactions
                                 by default.  See README for more information.
     
     - throws: Various errors in case something goes wrong!
     */
    public static func initWithSQLStore(momdInbundleID bundleID: String?, momdName: String, sqliteFilename: String?, options: [AnyHashable: Any]? = nil, serialTX: Bool = false) throws {
        var bundle: Bundle!
        
        if bundleID == nil {
            bundle = Bundle.main
        } else {
            guard let idBundle = Bundle(identifier: bundleID!) else {
                throw CdInitFailure.invalidBundle
            }
            bundle = idBundle
        }
        
        var actualMomdName: NSString = momdName as NSString
        if actualMomdName.pathExtension == "momd" {
            actualMomdName = actualMomdName.deletingPathExtension as NSString
        }
        
        guard let momdURL = bundle.url(forResource: actualMomdName as String, withExtension: "momd") else {
            throw CdInitFailure.invalidManagedObjectModel
        }
        
        var sqliteURL: URL? = nil
        if let filename = sqliteFilename {
            let documentDirectories = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            let documentDirectory   = documentDirectories[documentDirectories.count - 1]
            sqliteURL               = documentDirectory.appendingPathComponent(filename)
            
            try FileManager.default.createDirectory(at: documentDirectory, withIntermediateDirectories: true, attributes: nil)
        }
        
        
        try Cd.initWithSQLStore(momdURL: momdURL, sqliteURL: sqliteURL, options: options, serialTX: serialTX)
    }
    
    /*
     *  -------------------- Object Query Support ----------------------
     */
    
    /**
     This instantiates a CdFetchRequest object, which is used to created chained
     object queries.
     
     Be aware that the fetch will execute against the context of the calling thread.
     If run from the main thread, the fetch is on the main thread context.  If called
     from inside a transaction, the fetch is run against the context of the 
     transaction.
     
     - parameter objectClass: The managed object type to query.  Must inherit from
                               CdManagedObject
     
     - returns: The CdFetchRequest object ready to be configured and then fetched.
    */
    public static func objects<T: CdManagedObject>(_ objectClass: T.Type) -> CdFetchRequest<T> {
        return CdFetchRequest<T>()
    }

    /**
     A macro to query for a specific object based on its id (or primary key).
     
     - parameter objectClass: The object type to query for
     - parameter idValue:     The value of the ID
     - parameter key:         The name of the ID column (default = "id")
     
     - throws: Throws a general fetch exception if it occurs.
     
     - returns: The object that was found, or nil
     */
    public static func objectWithID<T: CdManagedObject>(_ objectClass: T.Type, idValue: AnyObject, key: String = "id") throws -> T? {
        return try Cd.objects(objectClass).filter("\(key) == %@", idValue).fetchOne()
    }
    
    /**
     A macro to query for objects based on their ids (or primary keys).
     
     - parameter objectClass: The object type to query for
     - parameter idValues:    The value of the IDS to search for
     - parameter key:         The name of the ID column (default = "id")
     
     - throws: Throws a general fetch exception if it occurs.
     
     - returns: The objects that were found
     */
    public static func objectsWithIDs<T: CdManagedObject>(_ objectClass: T.Type, idValues: [AnyObject], key: String = "id") throws -> [T] {
        return try Cd.objects(objectClass).filter("\(key) IN %@", idValues).fetch()
    }
    
    /**
     This is a wrapper around the normal NSFetchedResultsController that ensures
     you are using the main thread context.
     
     - parameter fetchRequest:       The NSFetchRequest to use.  You can use the .nsFetchRequest
                                     property of a CdFetchRequest.
     - parameter sectionNameKeyPath: The section name key path
     - parameter cacheName:          The cache name
     
     - returns: The initialized NSFetchedResultsController
     */
    public static func newFetchedResultsController<T: CdManagedObject>(_ fetchRequest: NSFetchRequest<T>, sectionNameKeyPath: String?, cacheName: String?) -> NSFetchedResultsController<T> {
        return NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CdManagedObjectContext.mainThreadContext(), sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
    }
    
    /*
     *  -------------------- Object Query Swift 3 Support ----------------------
     */
    
    /**
     This instantiates a CdFetchRequest object, which is used to created chained
     object queries.
     
     Be aware that the fetch will execute against the context of the calling thread.
     If run from the main thread, the fetch is on the main thread context.  If called
     from inside a transaction, the fetch is run against the context of the
     transaction.
     
     - parameter objectClass: The managed object type to query.  Must inherit from
                              CdManagedObject
     
     - returns: The CdFetchRequest object ready to be configured and then fetched.
     */
    public static func objectQuery<T: CdManagedObject>(for: T.Type) -> CdFetchRequest<T> {
        return CdFetchRequest<T>()
    }
    
    /**
     This instantiates a CdFetchRequest object, which is used to created chained
     object queries.
     
     Be aware that the fetch will execute against the context of the calling thread.
     If run from the main thread, the fetch is on the main thread context.  If called
     from inside a transaction, the fetch is run against the context of the
     transaction.
     
     - parameter objectClass: The managed object type to query.  Must inherit from
     CdManagedObject
     
     - returns: The CdFetchRequest object ready to be configured and then fetched.
     */
    public static func dictionaryQuery<T: CdManagedObject>(for: T.Type) -> CdFetchRequest<NSDictionary> {
        return CdFetchRequest<NSDictionary>(entityName: "\(T.self)")
    }
    
    /*
     *  -------------------- Object Lifecycle ----------------------
     */
    
    /**
     Create a new CdManagedObject.  If this method is called from the main thread the object must be
     transient.  Otherwise it must be called from inside a transaction.
     
     - parameter entityType: The entity type you would like to create.
     - parameter transient:  If false, the object will be automatically inserted
                             into the current transaction context.
     
     - returns: The created object.
     */
    public static func create<T: CdManagedObject>(_ entityType: T.Type, transient: Bool = false) throws -> T {
        guard let entDesc = NSEntityDescription.entity(forEntityName: "\(entityType)", in: CdManagedObjectContext.mainThreadContext()) else {
            Cd.raise("Could not create entity description for \(entityType)")
        }
        
        if transient {
            return CdManagedObject(entity: entDesc, insertInto:  nil) as! T
        }
        
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot create a non-transient object in the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only create a new managed object from inside a valid transaction.")
        }
        
        let object = CdManagedObject(entity: entDesc, insertInto: currentContext) as! T
        try currentContext.obtainPermanentIDs(for: [object])
        return object
    }
    
    /**
     Create many new CdManagedObjects.  If this method is called from the main thread the objects must be
     transient.  Otherwise it must be called from inside a transaction.
     
     - parameter entityType: The entity type you would like to create.
     - parameter count:      How many objects to create
     - parameter transient:  If false, the object will be automatically inserted
     into the current transaction context.
     
     - returns: The created object.
     */
    public static func create<T: CdManagedObject>(_ entityType: T.Type, count: Int, transient: Bool = false) throws -> [T] {
        guard count > 0 else {
            return []
        }
        
        guard let entDesc = NSEntityDescription.entity(forEntityName: "\(entityType)", in: CdManagedObjectContext.mainThreadContext()) else {
            Cd.raise("Could not create entity description for \(entityType)")
        }
        
        if transient {
            var result: [T] = []
            for _ in 0 ..< count {
                result.append(CdManagedObject(entity: entDesc, insertInto:  nil) as! T)
            }
            return result
        }
        
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot create non-transient objects in the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only create new managed objects from inside a valid transaction.")
        }
        
        var result: [T] = []
        for _ in 0 ..< count {
            let object = CdManagedObject(entity: entDesc, insertInto: currentContext) as! T
            try currentContext.obtainPermanentIDs(for: [object])
            result.append(object)
        }
        return result
    }
    
    /**
     Inserts the transient object into the current transaction context.  The object must have been created
     with the transient flag set to true, and not be inserted into any other context yet.
     
     - parameter object: The object to insert into the current context.
     */
    public static func insert(_ object: CdManagedObject) {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot insert an object from the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only insert a new managed object from inside a valid transaction.")
        }
        
        if currentContext === object.managedObjectContext {
            return
        }
        
        if object.managedObjectContext != nil {
            Cd.raise("You cannot insert an object into a context that already belongs to another context.  Use Cd.useInCurrentContext instead.")
        }
        
        let keys: [String] = object.entity.attributesByName.keys.map {$0}
        let properties = object.dictionaryWithValues(forKeys: keys)
        currentContext.insert(object)
        currentContext.refresh(object, mergeChanges: true)
        object.setValuesForKeys(properties)
    }
    
    /**
     Inserts the transient objects into the current transaction context.  The objects must have been created
     with the transient flag set to true, and not be inserted into any other context yet.
     
     - parameter objects: The objects to insert into the current context.
     */
    public static func insert(_ objects: [CdManagedObject]) {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot insert an object from the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only insert a new managed object from inside a valid transaction.")
        }

        for object in objects {
            if currentContext === object.managedObjectContext {
                continue
            }
            
            if object.managedObjectContext != nil {
                Cd.raise("You cannot insert an object into a context that already belongs to another context.  Use Cd.useInCurrentContext instead.")
            }
            
            let keys: [String] = object.entity.attributesByName.keys.map {$0}
            let properties = object.dictionaryWithValues(forKeys: keys)
            currentContext.insert(object)
            currentContext.refresh(object, mergeChanges: true)
            object.setValuesForKeys(properties)
        }
    }
    
    /**
     Delete the object from the current transaction context.  The object must exist and
     reside in the current transactional context.
     
     - parameter object: The object to delete from the current context.
     */
    public static func delete(_ object: CdManagedObject) {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot delete an object from the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only delete a managed object from inside a transaction.")
        }
        
        if currentContext !== object.managedObjectContext {
            Cd.raise("You may only delete a managed object from inside the transaction it belongs to.")
        }
        
        if object.managedObjectContext == nil {
            Cd.raise("You cannot delete an object that is not in a context.")
        }
        
        currentContext.delete(object)
    }
    
    /**
     Delete the objects from the current transaction context.  The objects must exist and
     reside in the current transactional context.
     
     - parameter objects: The objects to delete from the current context.
     */
    public static func delete(_ objects: [CdManagedObject]) {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You cannot delete an object from the main thread.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You may only delete a managed object from inside a transaction.")
        }
        
        for object in objects {
            if currentContext !== object.managedObjectContext {
                Cd.raise("You may only delete a managed object from inside the transaction it belongs to.")
            }
            
            if object.managedObjectContext == nil {
                Cd.raise("You cannot delete an object that is not in a context.")
            }
            
            currentContext.delete(object)
        }
    }
    
     
    /*
     *  -------------------- Transaction Support ----------------------
     */
    
    /**
     Initiate a database transaction asynchronously on a background thread.  A
     new CdManagedObjectContext will be created for the lifetime of the transaction.
     
     If the serial setting is true, transactions are executed serially, even if they
     occur in different contexts.
    
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            operate in a background thread, and may ultimately 
                            save back to the database using the Cd.commit() call.
    
                            The operation block is run asynchronously and will not 
                            occur on the main thread.  It will run on the private
                            queue of the write context.
     
                            It is important to note that no transactions can occur
                            on the main thread.  This will use a background write
                            context even if initially called from the main thread.
    */
    public static func transact(serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping () -> Void) {
        let useSerial = (serial ?? Cd.defaultSerialTransactions) || (serial != false && serialQueue != nil)
                
        /*  These blocks are different.  One calls performBlock as normal (useSerial = false).
            The other calls performBlockAndWait inside of an async serial queue (useSerial = true)
         */
        
        if useSerial {
            (serialQueue ?? CdManagedObjectContext.serialTransactionQueue).async {
                let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()
                newWriteContext.performAndWait() {
                    let prevInside = Thread.current.setInsideTransaction(true)
                    self.transactOperation(newWriteContext, operation: operation)
                    Thread.current.setInsideTransaction(prevInside)
                }
            }
        } else {
            let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()
            newWriteContext.perform {
                let prevInside = Thread.current.setInsideTransaction(true)
                self.transactOperation(newWriteContext, operation: operation)
                Thread.current.setInsideTransaction(prevInside)
            }
        }
    }
    
    /**
     Initiate a database transaction asynchronously on a background thread with an
     object from a different context.  A new CdManagedObjectContext will be created 
     for the lifetime of the transaction.
     
     If the serial setting is true, transactions are executed serially, even if they
     occur in different contexts.
     
     - parameter object:    An object from another context that you would like
                            to use in this transaction.
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            operate in a background thread, and may ultimately
                            save back to the database using the Cd.commit() call.
                            The operation is passed a CdManagedObject in the
                            new context that is derived from the first parameter.
     
     The operation block is run asynchronously and will not
     occur on the main thread.  It will run on the private
     queue of the write context.
     
     It is important to note that no transactions can occur
     on the main thread.  This will use a background write
     context even if initially called from the main thread.
     */
    public static func transactWith<T: CdManagedObject>(_ object: T, serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping (T?) -> Void) {
        Cd.transact(serial: serial, on: serialQueue) {
            operation(Cd.useInCurrentContext(object))
        }
    }

    /**
     Initiate a database transaction asynchronously on a background thread with an
     object from a different context.  A new CdManagedObjectContext will be created
     for the lifetime of the transaction.
     
     If the serial setting is true, transactions are executed serially, even if they
     occur in different contexts.
     
     - parameter objects:   An array of objects from another context that you would like
                            to use in this transaction.
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            operate in a background thread, and may ultimately
                            save back to the database using the Cd.commit() call.
                            The operation is passed an array of CdManagedObjects in the
                            new context that is derived from the first parameter.
     
     The operation block is run asynchronously and will not
     occur on the main thread.  It will run on the private
     queue of the write context.
     
     It is important to note that no transactions can occur
     on the main thread.  This will use a background write
     context even if initially called from the main thread.
     */
    public static func transactWith<T: CdManagedObject>(_ objects: [T], serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping ([T]) -> Void) {
        Cd.transact(serial: serial, on: serialQueue) {
            operation(Cd.useInCurrentContext(objects))
        }
    }

    /**
     Initiate a database transaction synchronously on the current thread.  A
     new CdManagedObjectContext will be created for the lifetime of the transaction.

     This function cannot be called from the main thread, to prevent potential
     deadlocks.  You can execute fetches and read data on the main thread without
     needing to wrap those operations in a transaction.
     
     If the serial setting is true, transactions are executed serially, even if they 
     occur in different contexts.  Recursive transactAndWait calls are not 
     dispatched serially, even if this setting is true (to prevent deadlocks).
     
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            should occur synchronously against the current background
                            thread.  Transactions may ultimately save back to the 
                            database using the Cd.commit() call.
     
                            The operation is synchronous and will block until complete.
                            It will execute on the context's private queue and may or
                            may not execute in a separate thread than the calling
                            thread.
    */
    public static func transactAndWait(serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping () -> Void) {
        if Thread.current.isMainThread {
            Cd.raise("You cannot perform transactAndWait on the main thread.  Use transact, or spin off a new background thread to call transactAndWait")
        }
        
        let useSerial = ((serial ?? Cd.defaultSerialTransactions) || (serial != false && serialQueue != nil)) && !Thread.current.insideTransaction()
        
        let operationBlock = {
            let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()
            newWriteContext.performAndWait {
                let prevInside = Thread.current.setInsideTransaction(true)
                self.transactOperation(newWriteContext, operation: operation)
                Thread.current.setInsideTransaction(prevInside)
            }
        }
        
        if useSerial {
            (serialQueue ?? CdManagedObjectContext.serialTransactionQueue).sync(execute: operationBlock)
        } else {
            operationBlock()
        }
    }
    
    
    /**
     Initiate a database transaction synchronously on the current thread.  A
     new CdManagedObjectContext will be created for the lifetime of the transaction.
     
     This function cannot be called from the main thread, to prevent potential
     deadlocks.  You can execute fetches and read data on the main thread without
     needing to wrap those operations in a transaction.
     
     If the serial setting is true, transactions are executed serially, even if they
     occur in different contexts.  Recursive transactAndWait calls are not
     dispatched serially, even if this setting is true (to prevent deadlocks).
     
     - parameter object:    An object from another context that you would like
                            to use in this transaction.
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            should occur synchronously against the current background
                            thread.  Transactions may ultimately save back to the
                            database using the Cd.commit() call.
     
     The operation is synchronous and will block until complete.
     It will execute on the context's private queue and may or
     may not execute in a separate thread than the calling
     thread.
     */
    public static func transactAndWaitWith<T: CdManagedObject>(_ object: T, serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping (T?) -> Void) {
        Cd.transactAndWait(serial: serial, on: serialQueue) {
            operation(Cd.useInCurrentContext(object))
        }
    }
    
    
    /**
     Initiate a database transaction synchronously on the current thread.  A
     new CdManagedObjectContext will be created for the lifetime of the transaction.
     
     This function cannot be called from the main thread, to prevent potential
     deadlocks.  You can execute fetches and read data on the main thread without
     needing to wrap those operations in a transaction.
     
     If the serial setting is true, transactions are executed serially, even if they
     occur in different contexts.  Recursive transactAndWait calls are not
     dispatched serially, even if this setting is true (to prevent deadlocks).
     
     - parameter objects:   An array of objects from another context that you would like
                            to use in this transaction.
     - parameter serial:    If defined, it will override the serial transaction
                            setting declared during initialization.  Leave nil
                            to use the default.
     - parameter on:        If non-nil, specifies the dispatch queue to run the
                            serial operation on.  This must be a serial dispatch
                            queue (not concurrent).  If this argument is non-nil,
                            the serial argument is assumed true unless specifically
                            passed as false.
     - parameter operation:	This function should be used for transactions that
                            should occur synchronously against the current background
                            thread.  Transactions may ultimately save back to the
                            database using the Cd.commit() call.
     
     The operation is synchronous and will block until complete.
     It will execute on the context's private queue and may or
     may not execute in a separate thread than the calling
     thread.
     */
    public static func transactAndWaitWith<T: CdManagedObject>(_ objects: [T], serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping ([T]) -> Void) {
        Cd.transactAndWait(serial: serial, on: serialQueue) {
            operation(Cd.useInCurrentContext(objects))
        }
    }
    
    
    /**
     This is the private helper method that conducts that actual transaction
     inside of the context's queue.
     
     - parameter fromContext: The managed object context we are transacting inside.
     - parameter operation:   The operation to perform.
     */
    fileprivate static func transactOperation(_ fromContext: CdManagedObjectContext, operation: () -> Void) {
        let currentThread    = Thread.current
        let existingContext  = currentThread.attachedContext()
        let existingNoCommit = currentThread.noImplicitCommit()
        
        currentThread.attachContext(fromContext)
        operation()
        
        if currentThread.noImplicitCommit() == false {
            try! Cd.commit()
        }
        
        currentThread.attachContext(existingContext)
        currentThread.setNoImplicitCommit(existingNoCommit)
    }
    
    /**
     Call this function from inside of a transaction to cancel the implicit
     commit that will occur after the transaction closure completes.
     */
    public static func cancelImplicitCommit() {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("The main thread does have a transaction context that can be committed.")
        }
        
        guard let _ = currentThread.attachedContext() else {
            Cd.raise("You must call this from inside a valid transaction.")
        }
        
        currentThread.setNoImplicitCommit(true)
    }
   
    /**
     Get the CdManagedObjectContext from inside of a valid transaction block.
     This can be used for various object manipulation functions (insertion,
     deletion, etc).
     
     - returns: The CdManagedObjectContext for the current transaction.
     */
    public static func transactionContext() -> CdManagedObjectContext {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("The main thread cannot have a transaction context.")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You must call this from inside a valid transaction.")
        }
        
        return currentContext
    }
    
    /**
     Allows you to refer to a foreign CdManagedObject (from another
     context) in your current context.
     
     - parameter object: A CdManagedObject that is suitable to use
                         in the current context (must have a permanent ID).
     */
    public static func useInCurrentContext<T: CdManagedObject>(_ object: T) -> T? {
        guard let currentContext = Thread.current.attachedContext() else {
            Cd.raise("You may only call useInCurrentContext from the main thread, or inside a valid transaction.")
        }
        
        if let originalContext = object.managedObjectContext , originalContext.hasChanges && originalContext != CdManagedObjectContext._mainThreadContext {
            Cd.raise("You cannot transfer an object from a context that has outstanding changes.  Make sure you call Cd.commit() from your transaction first.")
        }
        
        if object.objectID.isTemporaryID {
            Cd.raise("You cannot transfer an object without a permanent object ID.  This object may be transient or unsaved in its current context.")
        }
        
        if let myItem = (try? currentContext.existingObject(with: object.objectID)) as? T {
            currentContext.refresh(myItem, mergeChanges: false)
            return myItem
        }
        
        return nil
    }
    
    /**
     Use a CdManagedObject from a transaction back on the main thread.
     
     - parameter object:    The CdManagedObject you would like to use on the main thread.
     - parameter operation: The operation to perform.  The argument in this operation block
                            receives a version of the CdManagedObject on the main thread's
                            read-only context.
     */
    public static func onMainWith<T: CdManagedObject>(_ object: T?, operation: @escaping ((T?) -> Void)) {
        guard let obj = object else {
            DispatchQueue.main.async {
                operation(object)
            }
            return
        }
        
        DispatchQueue.main.async {
            operation(Cd.useInCurrentContext(obj))
        }
    }
    
    /**
     Use a CdManagedObject from a transaction back on the main thread.
     
     - parameter objects:   An array of CdManagedObjects you would like to use on the main thread.
     - parameter operation: The operation to perform.  The argument in this operation block
                            receives an array of CdManagedObjects on the main thread's
                            read-only context.  If an object could not be acquired on the main
                            thread it is not included in the array.
     */
    public static func onMainWith<T: CdManagedObject>(_ objects: [T], operation: @escaping (([T]) -> Void)) {
        DispatchQueue.main.async {
            var arr: [T] = []
            for obj in objects {
                if let mainObj = Cd.useInCurrentContext(obj) {
                    arr.append(mainObj)
                }
            }
            operation(arr)
        }
    }
    
    /**
     Allows you to refer to an array of foreign CdManagedObjects (from another
     context) in your current context.
     
     - parameter objects:   An array of CdManagedObjects that is suitable to use
                            in the current context (must have a permanent ID).
     - returns:             An array of objects suitable for use in the current
                            context.  Any object that could not be transferred is
                            left out of the return array.
     */
    public static func useInCurrentContext<T: CdManagedObject>(_ objects: [T]) -> [T] {
        var result: [T] = []
        
        for original in objects {
            if let contextObject = Cd.useInCurrentContext(original) {
                result.append(contextObject)
            }
        }
        
        return result
    }
    
    /**
     Commit any changes made inside of an active transaction.  Must be called from
     inside Cd.transact or Cd.transactAndWait.
    */
    public static func commit() throws {
        let currentThread = Thread.current
        if currentThread.isMainThread {
            Cd.raise("You can only commit changes inside of a transaction (the main thread is read-only).")
        }
        
        guard let currentContext = currentThread.attachedContext() else {
            Cd.raise("You can only commit changes inside of a transaction.")
        }
        
        /* We're inside of the context's performBlock -- only save it we have to */
        if currentContext.hasChanges {
            try currentContext.save()
        
            /* Save on our master write context */
            CdManagedObjectContext.saveMasterWriteContext()
        }
    }
    
    
    /*
     *  -------------------- Error Handling ----------------------
     */
    
    internal static func raise(_ reason: String) -> Never  {
        NSException(name: NSExceptionName(rawValue: "Cadmium Exception"), reason: reason, userInfo: nil).raise()
        fatalError("These usage exception cannot be caught")
    }
   
    
}

<MSG> some more object querying

<DFF> @@ -47,9 +47,17 @@ public class Cd {
      
      - returns: The CdFetchRequest object ready to be configured and then fetched.
     */
-    public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {
+    @inline(__always) public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {
         return CdFetchRequest<T>()
     }
+
+    public class func objectWithID<T: CdManagedObject>(objectClass: T.Type, id: AnyObject, key: String = "id") throws -> T? {
+        return try Cd.objects(objectClass).filter("\(key) == %@", id).fetchOne()
+    }
+    
+    public class func objectsWithIDs<T: CdManagedObject>(objectClass: T.Type, ids: [AnyObject], key: String = "id") throws -> [T] {
+        return try Cd.objects(objectClass).filter("\(key) IN %@", ids).fetch()
+    }
      
      
     /**

{'commit': 'diff --git a/Cadmium/Cd.swift b/Cadmium/Cd.swift\nindex 5c04b27..b427519 100644\n--- a/Cadmium/Cd.swift\n+++ b/Cadmium/Cd.swift\n@@ -47,9 +47,17 @@ public class Cd {\n      \n      - returns: The CdFetchRequest object ready to be configured and then fetched.\n     */\n-    public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {\n+    @inline(__always) public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {\n         return CdFetchRequest<T>()\n     }\n+\n+    public class func objectWithID<T: CdManagedObject>(objectClass: T.Type, id: AnyObject, key: String = "id") throws -> T? {\n+        return try Cd.objects(objectClass).filter("\\(key) == %@", id).fetchOne()\n+    }\n+    \n+    public class func objectsWithIDs<T: CdManagedObject>(objectClass: T.Type, ids: [AnyObject], key: String = "id") throws -> [T] {\n+        return try Cd.objects(objectClass).filter("\\(key) IN %@", ids).fetch()\n+    }\n      \n      \n     /**\n', 'commit_subject': 'some more object querying', 'repo_name': 'jmfieldman/Cadmium', 'committer': {'name': 'Jason Fieldman', 'email': '68c46a606457643eab92053c1c05574abb26f861@fieldman.org', 'time_sec': '1457913883', 'tz_offset': '-420', 'date': {'seconds': '1457913883'}}, 'commit_tree': '3f2d1a701ed006ed3723d964ed02e3ae3c33c7ad', 'commit_message': 'some more object querying\n', 'language_name': 'Swift', 'license': 'mit', 'hunks': '@@ -47,9 +47,17 @@ public class Cd {\n      \n      - returns: The CdFetchRequest object ready to be configured and then fetched.\n     */\n-    public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {\n+    @inline(__always) public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {\n         return CdFetchRequest<T>()\n     }\n+\n+    public class func objectWithID<T: CdManagedObject>(objectClass: T.Type, id: AnyObject, key: String = "id") throws -> T? {\n+        return try Cd.objects(objectClass).filter("\\(key) == %@", id).fetchOne()\n+    }\n+    \n+    public class func objectsWithIDs<T: CdManagedObject>(objectClass: T.Type, ids: [AnyObject], key: String = "id") throws -> [T] {\n+        return try Cd.objects(objectClass).filter("\\(key) IN %@", ids).fetch()\n+    }\n      \n      \n     /**\n', 'addition_count': 9, 'deletion_count': 1, 'src_file': 'a/Cadmium/Cd.swift', 'tgt_file': 'b/Cadmium/Cd.swift', 'file_extension': '.swift', 'before_file': '//\n//  Cd.swift\n//  Cadmium\n//\n//  Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Cadmium\n//\n//  Permission is hereby granted, free of charge, to any person obtaining a copy\n//  of this software and associated documentation files (the "Software"), to deal\n//  in the Software without restriction, including without limitation the rights\n//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//  copies of the Software, and to permit persons to whom the Software is\n//  furnished to do so, subject to the following conditions:\n//\n//  The above copyright notice and this permission notice shall be included in\n//  all copies or substantial portions of the Software.\n//\n//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n//  THE SOFTWARE.\n\nimport Foundation\nimport CoreData\n\n\npublic class Cd {\n\n    \n    /*\n     *  -------------------- Settings -----------------------\n     */\n    \n    /** If true, Cadmium will default to using serial transactions. */\n    internal static var defaultSerialTransactions: Bool = false\n    \n    /*\n     *  -------------------- Initialization ----------------------\n     */\n    \n    /**\n     Initialize the Cadmium engine with the URLs for the managed object model\n     and the SQLite store.\n    \n     \n     - returns: The CdFetchRequest object ready to be configured and then fetched.\n    */\n    public class func objects<T: CdManagedObject>(objectClass: T.Type) -> CdFetchRequest<T> {\n        return CdFetchRequest<T>()\n    }\n     \n     \n    /**\n        \n        CdManagedObjectContext.initializeMasterContexts(coordinator: psc)\n    }\n    \n    /**\n     Initialize the Cadmium engine with a SQLite store.  This initializer helps\n     wrap up some of the menial tasks of drilling down exact URLs for resources.\n     \n     - parameter bundleID:       Pass the bundle identifier that contains your\n                                 managed object model file.  This is typically\n                                 something like com.yourcompany.yourapp, or\n                                 com.yourcompany.yourframework.\n     \n                                 If you pass nil to this parameter it will look\n                                 in the main bundle (which will fail if the\n                                 object model is inside of another framework.\n     - parameter momdName:       The name of the managed object model\n     - parameter sqliteFilename: The name of the SQLite file you are storing data\n                                 in.  The initializer will append this filename\n                                 to the user\'s document directory.\n                                 If nil, Cadmium will use an in-memory store.\n     - parameter serialTX:       If Cadmium should use serial transactions\n                                 by default.  See README for more information.\n     \n     - throws: Various errors in case something goes wrong!\n     */\n    public static func initWithSQLStore(momdInbundleID bundleID: String?, momdName: String, sqliteFilename: String?, options: [AnyHashable: Any]? = nil, serialTX: Bool = false) throws {\n        var bundle: Bundle!\n        \n        if bundleID == nil {\n            bundle = Bundle.main\n        } else {\n            guard let idBundle = Bundle(identifier: bundleID!) else {\n                throw CdInitFailure.invalidBundle\n            }\n            bundle = idBundle\n        }\n        \n        var actualMomdName: NSString = momdName as NSString\n        if actualMomdName.pathExtension == "momd" {\n            actualMomdName = actualMomdName.deletingPathExtension as NSString\n        }\n        \n        guard let momdURL = bundle.url(forResource: actualMomdName as String, withExtension: "momd") else {\n            throw CdInitFailure.invalidManagedObjectModel\n        }\n        \n        var sqliteURL: URL? = nil\n        if let filename = sqliteFilename {\n            let documentDirectories = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)\n            let documentDirectory   = documentDirectories[documentDirectories.count - 1]\n            sqliteURL               = documentDirectory.appendingPathComponent(filename)\n            \n            try FileManager.default.createDirectory(at: documentDirectory, withIntermediateDirectories: true, attributes: nil)\n        }\n        \n        \n        try Cd.initWithSQLStore(momdURL: momdURL, sqliteURL: sqliteURL, options: options, serialTX: serialTX)\n    }\n    \n    /*\n     *  -------------------- Object Query Support ----------------------\n     */\n    \n    /**\n     This instantiates a CdFetchRequest object, which is used to created chained\n     object queries.\n     \n     Be aware that the fetch will execute against the context of the calling thread.\n     If run from the main thread, the fetch is on the main thread context.  If called\n     from inside a transaction, the fetch is run against the context of the \n     transaction.\n     \n     - parameter objectClass: The managed object type to query.  Must inherit from\n                               CdManagedObject\n     \n     - returns: The CdFetchRequest object ready to be configured and then fetched.\n    */\n    public static func objects<T: CdManagedObject>(_ objectClass: T.Type) -> CdFetchRequest<T> {\n        return CdFetchRequest<T>()\n    }\n\n    /**\n     A macro to query for a specific object based on its id (or primary key).\n     \n     - parameter objectClass: The object type to query for\n     - parameter idValue:     The value of the ID\n     - parameter key:         The name of the ID column (default = "id")\n     \n     - throws: Throws a general fetch exception if it occurs.\n     \n     - returns: The object that was found, or nil\n     */\n    public static func objectWithID<T: CdManagedObject>(_ objectClass: T.Type, idValue: AnyObject, key: String = "id") throws -> T? {\n        return try Cd.objects(objectClass).filter("\\(key) == %@", idValue).fetchOne()\n    }\n    \n    /**\n     A macro to query for objects based on their ids (or primary keys).\n     \n     - parameter objectClass: The object type to query for\n     - parameter idValues:    The value of the IDS to search for\n     - parameter key:         The name of the ID column (default = "id")\n     \n     - throws: Throws a general fetch exception if it occurs.\n     \n     - returns: The objects that were found\n     */\n    public static func objectsWithIDs<T: CdManagedObject>(_ objectClass: T.Type, idValues: [AnyObject], key: String = "id") throws -> [T] {\n        return try Cd.objects(objectClass).filter("\\(key) IN %@", idValues).fetch()\n    }\n    \n    /**\n     This is a wrapper around the normal NSFetchedResultsController that ensures\n     you are using the main thread context.\n     \n     - parameter fetchRequest:       The NSFetchRequest to use.  You can use the .nsFetchRequest\n                                     property of a CdFetchRequest.\n     - parameter sectionNameKeyPath: The section name key path\n     - parameter cacheName:          The cache name\n     \n     - returns: The initialized NSFetchedResultsController\n     */\n    public static func newFetchedResultsController<T: CdManagedObject>(_ fetchRequest: NSFetchRequest<T>, sectionNameKeyPath: String?, cacheName: String?) -> NSFetchedResultsController<T> {\n        return NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CdManagedObjectContext.mainThreadContext(), sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)\n    }\n    \n    /*\n     *  -------------------- Object Query Swift 3 Support ----------------------\n     */\n    \n    /**\n     This instantiates a CdFetchRequest object, which is used to created chained\n     object queries.\n     \n     Be aware that the fetch will execute against the context of the calling thread.\n     If run from the main thread, the fetch is on the main thread context.  If called\n     from inside a transaction, the fetch is run against the context of the\n     transaction.\n     \n     - parameter objectClass: The managed object type to query.  Must inherit from\n                              CdManagedObject\n     \n     - returns: The CdFetchRequest object ready to be configured and then fetched.\n     */\n    public static func objectQuery<T: CdManagedObject>(for: T.Type) -> CdFetchRequest<T> {\n        return CdFetchRequest<T>()\n    }\n    \n    /**\n     This instantiates a CdFetchRequest object, which is used to created chained\n     object queries.\n     \n     Be aware that the fetch will execute against the context of the calling thread.\n     If run from the main thread, the fetch is on the main thread context.  If called\n     from inside a transaction, the fetch is run against the context of the\n     transaction.\n     \n     - parameter objectClass: The managed object type to query.  Must inherit from\n     CdManagedObject\n     \n     - returns: The CdFetchRequest object ready to be configured and then fetched.\n     */\n    public static func dictionaryQuery<T: CdManagedObject>(for: T.Type) -> CdFetchRequest<NSDictionary> {\n        return CdFetchRequest<NSDictionary>(entityName: "\\(T.self)")\n    }\n    \n    /*\n     *  -------------------- Object Lifecycle ----------------------\n     */\n    \n    /**\n     Create a new CdManagedObject.  If this method is called from the main thread the object must be\n     transient.  Otherwise it must be called from inside a transaction.\n     \n     - parameter entityType: The entity type you would like to create.\n     - parameter transient:  If false, the object will be automatically inserted\n                             into the current transaction context.\n     \n     - returns: The created object.\n     */\n    public static func create<T: CdManagedObject>(_ entityType: T.Type, transient: Bool = false) throws -> T {\n        guard let entDesc = NSEntityDescription.entity(forEntityName: "\\(entityType)", in: CdManagedObjectContext.mainThreadContext()) else {\n            Cd.raise("Could not create entity description for \\(entityType)")\n        }\n        \n        if transient {\n            return CdManagedObject(entity: entDesc, insertInto:  nil) as! T\n        }\n        \n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot create a non-transient object in the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only create a new managed object from inside a valid transaction.")\n        }\n        \n        let object = CdManagedObject(entity: entDesc, insertInto: currentContext) as! T\n        try currentContext.obtainPermanentIDs(for: [object])\n        return object\n    }\n    \n    /**\n     Create many new CdManagedObjects.  If this method is called from the main thread the objects must be\n     transient.  Otherwise it must be called from inside a transaction.\n     \n     - parameter entityType: The entity type you would like to create.\n     - parameter count:      How many objects to create\n     - parameter transient:  If false, the object will be automatically inserted\n     into the current transaction context.\n     \n     - returns: The created object.\n     */\n    public static func create<T: CdManagedObject>(_ entityType: T.Type, count: Int, transient: Bool = false) throws -> [T] {\n        guard count > 0 else {\n            return []\n        }\n        \n        guard let entDesc = NSEntityDescription.entity(forEntityName: "\\(entityType)", in: CdManagedObjectContext.mainThreadContext()) else {\n            Cd.raise("Could not create entity description for \\(entityType)")\n        }\n        \n        if transient {\n            var result: [T] = []\n            for _ in 0 ..< count {\n                result.append(CdManagedObject(entity: entDesc, insertInto:  nil) as! T)\n            }\n            return result\n        }\n        \n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot create non-transient objects in the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only create new managed objects from inside a valid transaction.")\n        }\n        \n        var result: [T] = []\n        for _ in 0 ..< count {\n            let object = CdManagedObject(entity: entDesc, insertInto: currentContext) as! T\n            try currentContext.obtainPermanentIDs(for: [object])\n            result.append(object)\n        }\n        return result\n    }\n    \n    /**\n     Inserts the transient object into the current transaction context.  The object must have been created\n     with the transient flag set to true, and not be inserted into any other context yet.\n     \n     - parameter object: The object to insert into the current context.\n     */\n    public static func insert(_ object: CdManagedObject) {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot insert an object from the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only insert a new managed object from inside a valid transaction.")\n        }\n        \n        if currentContext === object.managedObjectContext {\n            return\n        }\n        \n        if object.managedObjectContext != nil {\n            Cd.raise("You cannot insert an object into a context that already belongs to another context.  Use Cd.useInCurrentContext instead.")\n        }\n        \n        let keys: [String] = object.entity.attributesByName.keys.map {$0}\n        let properties = object.dictionaryWithValues(forKeys: keys)\n        currentContext.insert(object)\n        currentContext.refresh(object, mergeChanges: true)\n        object.setValuesForKeys(properties)\n    }\n    \n    /**\n     Inserts the transient objects into the current transaction context.  The objects must have been created\n     with the transient flag set to true, and not be inserted into any other context yet.\n     \n     - parameter objects: The objects to insert into the current context.\n     */\n    public static func insert(_ objects: [CdManagedObject]) {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot insert an object from the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only insert a new managed object from inside a valid transaction.")\n        }\n\n        for object in objects {\n            if currentContext === object.managedObjectContext {\n                continue\n            }\n            \n            if object.managedObjectContext != nil {\n                Cd.raise("You cannot insert an object into a context that already belongs to another context.  Use Cd.useInCurrentContext instead.")\n            }\n            \n            let keys: [String] = object.entity.attributesByName.keys.map {$0}\n            let properties = object.dictionaryWithValues(forKeys: keys)\n            currentContext.insert(object)\n            currentContext.refresh(object, mergeChanges: true)\n            object.setValuesForKeys(properties)\n        }\n    }\n    \n    /**\n     Delete the object from the current transaction context.  The object must exist and\n     reside in the current transactional context.\n     \n     - parameter object: The object to delete from the current context.\n     */\n    public static func delete(_ object: CdManagedObject) {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot delete an object from the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only delete a managed object from inside a transaction.")\n        }\n        \n        if currentContext !== object.managedObjectContext {\n            Cd.raise("You may only delete a managed object from inside the transaction it belongs to.")\n        }\n        \n        if object.managedObjectContext == nil {\n            Cd.raise("You cannot delete an object that is not in a context.")\n        }\n        \n        currentContext.delete(object)\n    }\n    \n    /**\n     Delete the objects from the current transaction context.  The objects must exist and\n     reside in the current transactional context.\n     \n     - parameter objects: The objects to delete from the current context.\n     */\n    public static func delete(_ objects: [CdManagedObject]) {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You cannot delete an object from the main thread.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You may only delete a managed object from inside a transaction.")\n        }\n        \n        for object in objects {\n            if currentContext !== object.managedObjectContext {\n                Cd.raise("You may only delete a managed object from inside the transaction it belongs to.")\n            }\n            \n            if object.managedObjectContext == nil {\n                Cd.raise("You cannot delete an object that is not in a context.")\n            }\n            \n            currentContext.delete(object)\n        }\n    }\n    \n     \n    /*\n     *  -------------------- Transaction Support ----------------------\n     */\n    \n    /**\n     Initiate a database transaction asynchronously on a background thread.  A\n     new CdManagedObjectContext will be created for the lifetime of the transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they\n     occur in different contexts.\n    \n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            operate in a background thread, and may ultimately \n                            save back to the database using the Cd.commit() call.\n    \n                            The operation block is run asynchronously and will not \n                            occur on the main thread.  It will run on the private\n                            queue of the write context.\n     \n                            It is important to note that no transactions can occur\n                            on the main thread.  This will use a background write\n                            context even if initially called from the main thread.\n    */\n    public static func transact(serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping () -> Void) {\n        let useSerial = (serial ?? Cd.defaultSerialTransactions) || (serial != false && serialQueue != nil)\n                \n        /*  These blocks are different.  One calls performBlock as normal (useSerial = false).\n            The other calls performBlockAndWait inside of an async serial queue (useSerial = true)\n         */\n        \n        if useSerial {\n            (serialQueue ?? CdManagedObjectContext.serialTransactionQueue).async {\n                let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()\n                newWriteContext.performAndWait() {\n                    let prevInside = Thread.current.setInsideTransaction(true)\n                    self.transactOperation(newWriteContext, operation: operation)\n                    Thread.current.setInsideTransaction(prevInside)\n                }\n            }\n        } else {\n            let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()\n            newWriteContext.perform {\n                let prevInside = Thread.current.setInsideTransaction(true)\n                self.transactOperation(newWriteContext, operation: operation)\n                Thread.current.setInsideTransaction(prevInside)\n            }\n        }\n    }\n    \n    /**\n     Initiate a database transaction asynchronously on a background thread with an\n     object from a different context.  A new CdManagedObjectContext will be created \n     for the lifetime of the transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they\n     occur in different contexts.\n     \n     - parameter object:    An object from another context that you would like\n                            to use in this transaction.\n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            operate in a background thread, and may ultimately\n                            save back to the database using the Cd.commit() call.\n                            The operation is passed a CdManagedObject in the\n                            new context that is derived from the first parameter.\n     \n     The operation block is run asynchronously and will not\n     occur on the main thread.  It will run on the private\n     queue of the write context.\n     \n     It is important to note that no transactions can occur\n     on the main thread.  This will use a background write\n     context even if initially called from the main thread.\n     */\n    public static func transactWith<T: CdManagedObject>(_ object: T, serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping (T?) -> Void) {\n        Cd.transact(serial: serial, on: serialQueue) {\n            operation(Cd.useInCurrentContext(object))\n        }\n    }\n\n    /**\n     Initiate a database transaction asynchronously on a background thread with an\n     object from a different context.  A new CdManagedObjectContext will be created\n     for the lifetime of the transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they\n     occur in different contexts.\n     \n     - parameter objects:   An array of objects from another context that you would like\n                            to use in this transaction.\n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            operate in a background thread, and may ultimately\n                            save back to the database using the Cd.commit() call.\n                            The operation is passed an array of CdManagedObjects in the\n                            new context that is derived from the first parameter.\n     \n     The operation block is run asynchronously and will not\n     occur on the main thread.  It will run on the private\n     queue of the write context.\n     \n     It is important to note that no transactions can occur\n     on the main thread.  This will use a background write\n     context even if initially called from the main thread.\n     */\n    public static func transactWith<T: CdManagedObject>(_ objects: [T], serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping ([T]) -> Void) {\n        Cd.transact(serial: serial, on: serialQueue) {\n            operation(Cd.useInCurrentContext(objects))\n        }\n    }\n\n    /**\n     Initiate a database transaction synchronously on the current thread.  A\n     new CdManagedObjectContext will be created for the lifetime of the transaction.\n\n     This function cannot be called from the main thread, to prevent potential\n     deadlocks.  You can execute fetches and read data on the main thread without\n     needing to wrap those operations in a transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they \n     occur in different contexts.  Recursive transactAndWait calls are not \n     dispatched serially, even if this setting is true (to prevent deadlocks).\n     \n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            should occur synchronously against the current background\n                            thread.  Transactions may ultimately save back to the \n                            database using the Cd.commit() call.\n     \n                            The operation is synchronous and will block until complete.\n                            It will execute on the context\'s private queue and may or\n                            may not execute in a separate thread than the calling\n                            thread.\n    */\n    public static func transactAndWait(serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping () -> Void) {\n        if Thread.current.isMainThread {\n            Cd.raise("You cannot perform transactAndWait on the main thread.  Use transact, or spin off a new background thread to call transactAndWait")\n        }\n        \n        let useSerial = ((serial ?? Cd.defaultSerialTransactions) || (serial != false && serialQueue != nil)) && !Thread.current.insideTransaction()\n        \n        let operationBlock = {\n            let newWriteContext = CdManagedObjectContext.newBackgroundWriteContext()\n            newWriteContext.performAndWait {\n                let prevInside = Thread.current.setInsideTransaction(true)\n                self.transactOperation(newWriteContext, operation: operation)\n                Thread.current.setInsideTransaction(prevInside)\n            }\n        }\n        \n        if useSerial {\n            (serialQueue ?? CdManagedObjectContext.serialTransactionQueue).sync(execute: operationBlock)\n        } else {\n            operationBlock()\n        }\n    }\n    \n    \n    /**\n     Initiate a database transaction synchronously on the current thread.  A\n     new CdManagedObjectContext will be created for the lifetime of the transaction.\n     \n     This function cannot be called from the main thread, to prevent potential\n     deadlocks.  You can execute fetches and read data on the main thread without\n     needing to wrap those operations in a transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they\n     occur in different contexts.  Recursive transactAndWait calls are not\n     dispatched serially, even if this setting is true (to prevent deadlocks).\n     \n     - parameter object:    An object from another context that you would like\n                            to use in this transaction.\n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            should occur synchronously against the current background\n                            thread.  Transactions may ultimately save back to the\n                            database using the Cd.commit() call.\n     \n     The operation is synchronous and will block until complete.\n     It will execute on the context\'s private queue and may or\n     may not execute in a separate thread than the calling\n     thread.\n     */\n    public static func transactAndWaitWith<T: CdManagedObject>(_ object: T, serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping (T?) -> Void) {\n        Cd.transactAndWait(serial: serial, on: serialQueue) {\n            operation(Cd.useInCurrentContext(object))\n        }\n    }\n    \n    \n    /**\n     Initiate a database transaction synchronously on the current thread.  A\n     new CdManagedObjectContext will be created for the lifetime of the transaction.\n     \n     This function cannot be called from the main thread, to prevent potential\n     deadlocks.  You can execute fetches and read data on the main thread without\n     needing to wrap those operations in a transaction.\n     \n     If the serial setting is true, transactions are executed serially, even if they\n     occur in different contexts.  Recursive transactAndWait calls are not\n     dispatched serially, even if this setting is true (to prevent deadlocks).\n     \n     - parameter objects:   An array of objects from another context that you would like\n                            to use in this transaction.\n     - parameter serial:    If defined, it will override the serial transaction\n                            setting declared during initialization.  Leave nil\n                            to use the default.\n     - parameter on:        If non-nil, specifies the dispatch queue to run the\n                            serial operation on.  This must be a serial dispatch\n                            queue (not concurrent).  If this argument is non-nil,\n                            the serial argument is assumed true unless specifically\n                            passed as false.\n     - parameter operation:\tThis function should be used for transactions that\n                            should occur synchronously against the current background\n                            thread.  Transactions may ultimately save back to the\n                            database using the Cd.commit() call.\n     \n     The operation is synchronous and will block until complete.\n     It will execute on the context\'s private queue and may or\n     may not execute in a separate thread than the calling\n     thread.\n     */\n    public static func transactAndWaitWith<T: CdManagedObject>(_ objects: [T], serial: Bool? = nil, on serialQueue: DispatchQueue? = nil, operation: @escaping ([T]) -> Void) {\n        Cd.transactAndWait(serial: serial, on: serialQueue) {\n            operation(Cd.useInCurrentContext(objects))\n        }\n    }\n    \n    \n    /**\n     This is the private helper method that conducts that actual transaction\n     inside of the context\'s queue.\n     \n     - parameter fromContext: The managed object context we are transacting inside.\n     - parameter operation:   The operation to perform.\n     */\n    fileprivate static func transactOperation(_ fromContext: CdManagedObjectContext, operation: () -> Void) {\n        let currentThread    = Thread.current\n        let existingContext  = currentThread.attachedContext()\n        let existingNoCommit = currentThread.noImplicitCommit()\n        \n        currentThread.attachContext(fromContext)\n        operation()\n        \n        if currentThread.noImplicitCommit() == false {\n            try! Cd.commit()\n        }\n        \n        currentThread.attachContext(existingContext)\n        currentThread.setNoImplicitCommit(existingNoCommit)\n    }\n    \n    /**\n     Call this function from inside of a transaction to cancel the implicit\n     commit that will occur after the transaction closure completes.\n     */\n    public static func cancelImplicitCommit() {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("The main thread does have a transaction context that can be committed.")\n        }\n        \n        guard let _ = currentThread.attachedContext() else {\n            Cd.raise("You must call this from inside a valid transaction.")\n        }\n        \n        currentThread.setNoImplicitCommit(true)\n    }\n   \n    /**\n     Get the CdManagedObjectContext from inside of a valid transaction block.\n     This can be used for various object manipulation functions (insertion,\n     deletion, etc).\n     \n     - returns: The CdManagedObjectContext for the current transaction.\n     */\n    public static func transactionContext() -> CdManagedObjectContext {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("The main thread cannot have a transaction context.")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You must call this from inside a valid transaction.")\n        }\n        \n        return currentContext\n    }\n    \n    /**\n     Allows you to refer to a foreign CdManagedObject (from another\n     context) in your current context.\n     \n     - parameter object: A CdManagedObject that is suitable to use\n                         in the current context (must have a permanent ID).\n     */\n    public static func useInCurrentContext<T: CdManagedObject>(_ object: T) -> T? {\n        guard let currentContext = Thread.current.attachedContext() else {\n            Cd.raise("You may only call useInCurrentContext from the main thread, or inside a valid transaction.")\n        }\n        \n        if let originalContext = object.managedObjectContext , originalContext.hasChanges && originalContext != CdManagedObjectContext._mainThreadContext {\n            Cd.raise("You cannot transfer an object from a context that has outstanding changes.  Make sure you call Cd.commit() from your transaction first.")\n        }\n        \n        if object.objectID.isTemporaryID {\n            Cd.raise("You cannot transfer an object without a permanent object ID.  This object may be transient or unsaved in its current context.")\n        }\n        \n        if let myItem = (try? currentContext.existingObject(with: object.objectID)) as? T {\n            currentContext.refresh(myItem, mergeChanges: false)\n            return myItem\n        }\n        \n        return nil\n    }\n    \n    /**\n     Use a CdManagedObject from a transaction back on the main thread.\n     \n     - parameter object:    The CdManagedObject you would like to use on the main thread.\n     - parameter operation: The operation to perform.  The argument in this operation block\n                            receives a version of the CdManagedObject on the main thread\'s\n                            read-only context.\n     */\n    public static func onMainWith<T: CdManagedObject>(_ object: T?, operation: @escaping ((T?) -> Void)) {\n        guard let obj = object else {\n            DispatchQueue.main.async {\n                operation(object)\n            }\n            return\n        }\n        \n        DispatchQueue.main.async {\n            operation(Cd.useInCurrentContext(obj))\n        }\n    }\n    \n    /**\n     Use a CdManagedObject from a transaction back on the main thread.\n     \n     - parameter objects:   An array of CdManagedObjects you would like to use on the main thread.\n     - parameter operation: The operation to perform.  The argument in this operation block\n                            receives an array of CdManagedObjects on the main thread\'s\n                            read-only context.  If an object could not be acquired on the main\n                            thread it is not included in the array.\n     */\n    public static func onMainWith<T: CdManagedObject>(_ objects: [T], operation: @escaping (([T]) -> Void)) {\n        DispatchQueue.main.async {\n            var arr: [T] = []\n            for obj in objects {\n                if let mainObj = Cd.useInCurrentContext(obj) {\n                    arr.append(mainObj)\n                }\n            }\n            operation(arr)\n        }\n    }\n    \n    /**\n     Allows you to refer to an array of foreign CdManagedObjects (from another\n     context) in your current context.\n     \n     - parameter objects:   An array of CdManagedObjects that is suitable to use\n                            in the current context (must have a permanent ID).\n     - returns:             An array of objects suitable for use in the current\n                            context.  Any object that could not be transferred is\n                            left out of the return array.\n     */\n    public static func useInCurrentContext<T: CdManagedObject>(_ objects: [T]) -> [T] {\n        var result: [T] = []\n        \n        for original in objects {\n            if let contextObject = Cd.useInCurrentContext(original) {\n                result.append(contextObject)\n            }\n        }\n        \n        return result\n    }\n    \n    /**\n     Commit any changes made inside of an active transaction.  Must be called from\n     inside Cd.transact or Cd.transactAndWait.\n    */\n    public static func commit() throws {\n        let currentThread = Thread.current\n        if currentThread.isMainThread {\n            Cd.raise("You can only commit changes inside of a transaction (the main thread is read-only).")\n        }\n        \n        guard let currentContext = currentThread.attachedContext() else {\n            Cd.raise("You can only commit changes inside of a transaction.")\n        }\n        \n        /* We\'re inside of the context\'s performBlock -- only save it we have to */\n        if currentContext.hasChanges {\n            try currentContext.save()\n        \n            /* Save on our master write context */\n            CdManagedObjectContext.saveMasterWriteContext()\n        }\n    }\n    \n    \n    /*\n     *  -------------------- Error Handling ----------------------\n     */\n    \n    internal static func raise(_ reason: String) -> Never  {\n        NSException(name: NSExceptionName(rawValue: "Cadmium Exception"), reason: reason, userInfo: nil).raise()\n        fatalError("These usage exception cannot be caught")\n    }\n   \n    \n}\n', 'message': 'some more object querying\n', 'author_name': 'Jason Fieldman', 'lang': 'swift'}