AlecrimCoreData

AlecrimCoreData is a framework to easily access Core Data objects in Swift.

Getting Started

Data Context

To use AlecrimCoreData you will need to create an inherited class from AlecrimCoreData.Context and declare a property or method for each entity in your data context like the example below:

import AlecrimCoreData

let dataContext = DataContext()!

class DataContext: Context {
    var people:      Table<Person>     { return Table<Person>(context: self) }
    var departments: Table<Department> { return Table<Department>(context: self) }
}

It’s important that properties (or methods) always return a new instance of a AlecrimCoreData.Table class.

Entities

It’s assumed that all entity classes was already created and added to the project. In the above section example there are two entities: Person and Department.

ACDGen

You can write managed object classes by hand or generate them using Xcode. Now you can also use ACDGen. ;-)

ACDGen app is a Core Data entity class generator made with AlecrimCoreData in mind. It is completely optional, but since it can also generate attribute class members for use in closure parameters, the experience using AlecrimCoreData is greatly improved.

You can open it from the Bin folder.

Usage

Fetching

Basic Fetching

Say you have an Entity called Person, related to a Department (as seen in various Apple Core Data documentation). To get all of the Person entities as an array, use the following methods:

for person in dataContext.people {
    println(person.firstName)
}

You can also skip some results:

let people = dataContext.people.skip(3)

Or take only some results:

let people = dataContext.people.skip(3).take(7)

Or, to return the results sorted by a property:

let peopleSorted = dataContext.people.orderBy({ $0.lastName })

Or, to return the results sorted by multiple properties:

let peopleSorted = dataContext.people
    .orderBy { $0.lastName }
    .thenBy { $0.firstName }

// OR

let peopleSorted = dataContext.people.sortBy("lastName,firstName")

Or, to return the results sorted by multiple properties with different attributes:

let peopleSorted = dataContext.people
    .orderByDescending { $0.lastName }
    .thenByAscending { $0.firstName }

// OR

let peopleSorted = dataContext.people.sortBy("lastName:0,firstName:1")

// OR

let peopleSorted = dataContext.people.sortBy("lastName:0:[cd],firstName:1:[cd]")

If you have a unique way of retrieving a single object from your data store (such as via an identifier), you can use the following code:

if let person = dataContext.people.first({ $0.identifier == 123 }) {
    println(person.name)
}

Count Entities

You can perform a count of the entities in your Persistent Store:

let count = dataContext.people.filter({ $0.lastName == "Smith" }).count()

Or:

let count = dataContext.people.count({ $0.lastName == "Smith" })

Aggregate Functions

You can use aggregate functions on a single attribute:

let total = dataContext.entities.sum({ $0.value })

The sum, min, max and average functions are supported. If the original property is an Optional the result will be an Optional too.

Selecting Only Some Attributes

You can specify an attribute to select:

let lastNames = dataContext.people.select({ $0.lastName }).distinct()

Or multiple properties to select:

let firstAndLastNames = dataContext.people.select(["firstName", "lastName"])

In both cases the result is an array of NSDictionary.

Advanced Fetching

If you want to be more specific with your search, you can use filter predicates:

let itemsPerPage = 10  

for pageNumber in 0..<5 {
    println("Page: \(pageNumber)")

    let peopleInCurrentPage = dataContext.people
        .filter { $0.department << [dept1, dept2] }
        .orderBy { $0.firstName }
        .thenBy { $0.lastName }
        .skip(pageNumber * itemsPerPage)
        .take(itemsPerPage)

    for person in peopleInCurrentPage {
        println("\(person.firstName) \(person.lastName) - \(person.department.name)")
    }
}

Collection Operators

You can use collection operators for “to many” relationships:

let crowdedDepartments = dataContext.departments.filter { $0.people.count > 100 }

Only the count operator is supported in this version.

Asynchronous Fetching

You can also fetch entities asynchronously and get the results later on main thread:

let fetchAsyncHandler = dataContext.people.fetchAsync { fetchedEntities, error in
    if error != nil {
        // Do a nice error handling here
    }
}

Returning an Array

The data is actually fetched from Persistent Store only when toArray() is explicitly or implicitly called. So you can combine and chain other methods before this.

let peopleArray = dataContext.people.toArray()

// OR

let peopleArray = dataContext.people.sortBy("firstName,lastName").toArray()

// OR

let theSmiths = dataContext.people
    .filter { $0.lastName == "Smith" }
    .orderBy { $0.firstName }

let count = theSmiths.count()
let array = theSmiths.toArray()

// OR

for person in dataContext.people.sortBy("firstName,lastName") {
    // .toArray() is called implicitly when enumerating
}

Converting to other class types

Call the to... method in the end of chain.

let fetchRequest = dataContext.people.toFetchRequest()

// OS X only
let arrayController = dataContext.people.toArrayController()

// iOS and OS X
let fetchedResultsController = dataContext.people.toFetchedResultsController() 

// iOS: returns a native NSFetchedResultsController class instance
// OS X: returns a NSFetchedResultsController equivalent class (ALCFetchedResultsController) instance
let fetchedResultsController = dataContext.people.toNativeFetchedResultsController()

Creating new Entities

When you need to create a new instance of an Entity, use:

let person = dataContext.people.createEntity()

You can also create or get first existing entity matching the criteria. If the entity does not exist, a new one is created and the specified attribute is assigned from the searched value automatically.

let person = dataContext.people.firstOrCreated({ $ 0.identifier == 123 })

Deleting Entities

To delete a single entity:

if let person = dataContext.people.first({ $0.identifier == 123 }) {
    dataContext.people.deleteEntity(person)
}

To delete many entities:

dataContext.departments.filter({ $0.people.count == 0 }).delete()

Saving

You can save the data context in the end, after all changes were made.

let person = dataContext.people.firstOrCreated({ $0.identifier == 9 })
person.firstName = "Christopher"
person.lastName = "Eccleston"
person.additionalInfo = "The best Doctor ever!"

// get success and error
let (success, error) = dataContext.save()
if success {
    // ...
}
else {
    println(error)
}

Threading

You can fetch and save entities in background calling a global function that creates a new data context instance for this:

// assuming that this department is saved and exists...
let department = dataContext.departments.first({ $0.identifier == 100 })!

// the closure below will run in a background context queue
performInBackground(dataContext) { bgc in
    if let person = bgc.people.first({ $0.identifier == 321 }) {
        // we must bring department to our background context
        person.department = department.inContext(bgc)! 
        person.otherData = "Other Data"
    }

    if bgc.save().0 {
        // ...
    }
}

Batch Updates

You can do batch updates on a single attribute using:

dataContext.entities.batchUpdate({ ($0.modified, true) }) { countOfUpdatedEntities, error in
    if error == nil {
        // ...
    }
}

Or you can specify multiples properties to update:

dataContext.entities.batchUpdate(["modified" : true, "dateModified" : NSDate()]) { countOfUpdatedEntities, error in
    if error == nil {
        // ...
    }
}

Advanced Configuration

You can use ContextOptions class for a custom configuration.

App Extensions

See Samples folder for a configuration example for use in the main app and its extensions.

iCloud Core Data sync

See Samples folder for a configuration example for iCloud Core Data sync.

Ensembles

See Samples folder for a configuration example for Ensembles.

Using

Minimum Requirements

  • Xcode 6.3
  • iOS 8.0 / OS X 10.10

Installation

Manually

You can add AlecrimCoreData as a git submodule, drag the AlecrimCoreData.xcodeproj file into your Xcode project and add the framework product as an embedded binary in your application target.

Branches and Contribution

  • master - The production branch. Clone or fork this repository for the latest copy.
  • develop - The active development branch. Pull requests should be directed to this branch.

If you want to contribute, please feel free to fork the repository and send pull requests with your fixes, suggestions and additions. :-)

Inspired By

Version History

  • 3.x - Swift framework: added attributes support and many other improvements
  • 2.x - Swift framework: public open source release
  • 1.x - Objective-C framework: private Alecrim team use


Contact

License

AlecrimCoreData is released under an MIT license. See LICENSE for more information.