Using Core Data in Swift

Core Data is a framework of great power, but it often comes with great frustration. Nevertheless, it remains a popular choice for many iOS developers. Swift is a language of great power, yet it promises ease and simplicity. Combining the two, can we use Swift to harness the power of Core Data, without the frustration?

In this talk Jesse Squires walks us through his attempt, and the new frustrations he found along the way. Presenting practical strategies for those moving away from an Objective-C model, he details the bugs and complexities you’ll likely encounter, how to work around them, and how Swift can bring new clarity to your model objects. We also learn how to harness Swift specific features, such as enums, in your NSManagedObject subclasses.

To view the code used in this talk you can visit Jesse’s GitHub.


What is Core Data? (0:10)

The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence.
- Core Data Programming Guide

Core Data is an Apple framework that provides object life-cycle management, object graph management and persistence. It’s backed by SQLite, but it’s not a relational database. Depending on your use case, Core Data may or may not be the right solution for you. Some of Core Data’s pros include the fact that it keeps the relationships between your objects in sync.

The Core Data Stack (3:12)

At the top level, we have the managed object context, within which you create objects. These are very similar to NSManagedObject, but they have extra behaviors and properties specific to Core Data. You create the objects and the context owns them; that is where you manipulate or modify these objects, create your relationships, and persist those objects through the context by saving them. The next step down is the Store Coordinator, which manages a collection of stores. In most cases you actually only have one store backed by SQLite, but there are different types. One of those types is an in-memory store, where you can use Core Data completely in-memory without worrying about persistence. Below that is the Persistent Store, represented by an NSPersistentStore object. That represents the actual store on-disk, just like a UI image would represent the image on disk or an NS bundle would represent the bundle on disk. Most of the time we’re only dealing with the context and managed objects; we don’t really worry about the rest of the stack once we set it up, for most use cases.

Why use Core Data? Why Swift? (5:26)

Core Data provides you with features that you need. In the words of Apple, it is “mature, unit tested, and optimized”. It’s also part of the iOS and OS X toolchain, so it is built into the platforms. Using Core Data is a good way to minimize third-party dependencies because it has integration with Xcode. Apple also continues to invest in it heavily; in previous years at WWDC there have been talks on new features in Core Data, and so there’s been long-term support for this framework. It’s also fairly popular, and so there are tons of resources online.

However, those resources are mostly in Objective-C, so why use Swift? Swift can bring clarity to our code, thereby making it easier in some ways to use Core Data. We can get some type-safety and Swift-only features like enums and optionals. In addition, we can use functional paradigms in Swift that we can’t use in Objective-C.

Swift + Core Data (8:08)

A warning about Swift: the tools are still immature. In general, the tools around Xcode and Swift are somewhat unstable, and though they are improving all the time, it can be frustrating.

The Core Data Stack (8:43)

The first step is setting up the Core Data stack. In the diagrams seen earlier, we want to encapsulate all the different pieces into some objects that we can reuse. This is basically the same boilerplate code as in Objective-C, but more pleasant. We start with our Core Data model, which is a struct so value-based. There is a name and a bundle, as well as other properties and methods to make dealing with the model easier.

struct CoreDataModel {
  let name: String
  let bundle: NSBundle
  init(name: String, bundle: NSBundle)
  // other properties & methods
}

After that is the stack, which will receive the model when we initialize it. The initializer will set up these three properties. For the storeType, we can provide default values, like a binary data store, SQLite store, or an in-memory store. We can do the same for concurrency, and specify the main queue as the default type here in Swift. Later on, if we wanted to unit test or create another stack, we can initialize with different stores or private queues.

let model = CoreDataModel(name: "MyModel", bundle: myBundle)
let stack = CoreDataStack(model: model,
                      storeType: NSSQLiteStoreType,
                concurrencyType: .MainQueueConcurrencyType)

// Use context
stack.managedObjectContext

Do not put these in the app delegate! The Xcode templates set up all of this boilerplate in the app delegate in a way that is not reusable in other parts of your app. This creates dependencies that you don’t want, and this code is then locked away in the app delegate where it never really should be anyway. Along those lines, use frameworks. With iOS 8 and particularly Swift, we can now create Cocoa Touch frameworks instead of using static libraries like we used to.

Creating Managed Objects (15:27)

Xcode will generate classes for you, which have never been that great. In Swift, they’re worse. mogenerator is a third-party library that a lot of people use for Objective-C, but it’s still in experimental stages for Swift with its last release on GitHub from September 2014. So again, this refers back to the tooling issue where some of the tools are still a bit immature.

When you create your objects, you have this visual editor and you can create their properties and their relationships. We can also have attribute validation, or custom rules and constraints on our data. We can make things optional or provide minimum/maximum values, and all these things are validated when you save.

The following block compares the typical Objective-C generated by Xcode and a comparable Swift class. In the Objective-C code, we have the employee object and all of its properties. However, we can’t tell which of them are optional in Core Data in your model file or if any of them have default values. The analogous Swift class is a lot cleaner thanks to cleaner syntax. Optionals allow for the absence of a value, and are marked by the question mark. Swift guarantees that anything that isn’t marked optional cannot be nil. @NSManaged in Swift has similar functionality to @dynamic in Objective-C. This basically informs the compiler that the storage and implementation of these properties are provided at runtime.

// Objective-C
@interface Employee: NSManagedObject

@property (nonatomic, retain) NSString *address;
@property (nonatomic, retain) NSDate *dateOfBirth;
@property (nonatomic, retain) NSString *email;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSDecimalNumber *salary;
@property (nonatomic, retain) NSNumber *status;

@end
// Swift
class Employee: NSManagedObject {
  @NSManaged var address: String?
  @NSManaged var dateOfBirth: NSDate
  @NSManaged var email: String?
  @NSManaged var name: String
  @NSManaged var salary: NSDecimalNumber
  @NSManaged var status: Int32
}

Another good thing is that Swift classes are namespaced by the module in which they’re in. This means that we have to prefix our classes with the module name. Xcode does not do this for you, so you have to do this manually after generating classes. No prefix will cause runtime crashes and other obscure errors.

Instantiating Managed Objects (22:07)

Now we want to use these objects in our code. There’s some boilerplate around initializing a managed object, and so we have an NSEntityDescription class that represents the entities in Core Data. These entity descriptions are the method by which you insert objects into Core Data, which can be a clunky and awkward process. To actually get an instance of our class, we write a helper function to get the name and put it into context.

// "Person"
NSString *name = [Person entityName];

@implementation NSManagedObject (Helpers)

+ (NSString *)entityName
{
  return NSStringFromClass([self class]);
}
@end

// Create new person
Person *person = [Person insertNewObjectInContext:context];

@implementation NSManagedObject (Helpers)
+ (instancetype)insertNewObjectInContext:(NSManagedObjectContext *)context
{
  return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
                                       inManagedObjectContext:context];
}

@end

In Swift, we have to use the Objective-C runtime getClass function, which returns the fully qualified name of MyApp.Person. But in Core Data, your object is just called Person, so we have to do some parsing. We create the EntityDescription with a name, put it in the context, and then call the NS Managed Object initializer.

// "MyApp.Person"
let fullName = NSStringFromClass(object_getClass(self))
extension NSManagedObject {
  class func entityName() -> String {
    let fullClassName = NSStringFromClass(object_getClass(self))
    let nameComponents = split(fullClassName) { $0 == "." }
    return last(nameComponents)!
  }
}
// "Person"
let entityName = Person.entityName()

// Create new person
let person = Person(context: context)
extension NSManagedObject {
  convenience init(context: NSManagedObjectContext) {
    let name = self.dynamicType.entityName()
    let entity = NSEntityDescription.entityForName(name,     inManagedObjectContext: context)!
    self.init(entity: entity,     insertIntoManagedObjectContext: context)
  }
}

// Designated initializer
class Employee: NSManagedObject {
  init(context: NSManagedObjectContext) {
    let entity = NSEntityDescription.entityForName("Employee",
    inManagedObjectContext: context)!
    super.init(entity: entity,
    insertIntoManagedObjectContext: context)
  }
}

The downside of this is that we have to do this for all of our classes. This is not very Swift-like; what we’ve done is basically just Objective-C with new syntax, which is not really our goal. We want to use Swift’s features and operate in the Swift mindset. The Objective-C way is not always the Swift way!

Embrace Swift!

Initializers (27:53)

The first step to embracing Swift is creating an actual designated initializer for these objects. All properties have to be assigned an initial value, and the designated initializer must fully init them. Convenience initializers are secondary and must call the designated initializer. Superclass initializers are not inherited in subclasses by default. Then, for NSManagedObject, we have this for the actual designated initializer.

// designated init
init(entity:insertIntoManagedObjectContext:)

// our convenience init
convenience init(context:)

However, Core Data bypasses initialization rules because of @NSManaged. Swift doesn’t know how to deal with properties that aren’t initialized because they’re provided at runtime. Instead, we can add an actual initializer that takes each of those parameters and have a dependency injection, where we provide each of those values.

class Employee: NSManagedObject {
  init(context: NSManagedObjectContext,
          name: String,
   dateOfBirth: NSDate,
        salary: NSDecimalNumber,
    employeeId: String = NSUUID().UUIDString,
         email: String? = nil,
       address: String? = nil) {
        // init
       }
}

Typealias (30:52)

The first Swift-specific feature that we can utilize is typealias. This is actually very simple and not specific to Core Data, so you can use this in other parts of your app. In this example, we typealias a string to EmployeeId, so we can refer to a type of EmployeeId throughout the code instead of string. With a typealias, we can make it clear what the string represents, and use the name as an actual type.

typealias EmployeeId = String
class Employee: NSManagedObject {
  @NSManaged var employeeId: EmployeeId
}

// Example
let id: EmployeeId = "12345"

Relationships (31:40)

In Swift 1.2, we got native set types. They’re value-based, generic, and better than NSSet, or a set of any object. When you have relationships between objects, like one-to-many, you will have a set that points to those objects. Unfortunately, Set is not compatible with Core Data, which doesn’t really know how to handle those.

Enums (32:25)

We can actually use enums in our managed objects. In this example, we have a Genre enum for bands and albums. Our NSManagedValue can be set private and our property can be public.

public class Band: NSManagedObject {
  @NSManaged private var genreValue: String
  public var genre: Genre {
    get {
      return Genre(rawValue: self.genreValue)!
    }
    set {
      self.genreValue = newValue.rawValue
    }
  }
}

// old
band.genre = "Black Metal"
// new
band.genre = .BlackMetal

However, Core Data doesn’t know about enums, so we have to use a private property for fetch requests. In this situation, we still have to use the value name for the key when we fetch. In some of these frameworks, we can really feel the baggage of Objective-C.

let fetch = NSFetchRequest(entityName: "Band")
fetch.predicate = NSPredicate(format: "genreValue == %@", genre)

Functional Paradigms (34:27)

The last thing is adding some of Swift’s functional paradigms with micro-libraries. Chris Eidhof gave a talk on tiny networking and how you can do a lot with very little code in Swift.

Saving (34:42)

To save a context, we have a save method and an error pointer that returns a Boolean value. To make it more Swift, we create a top-level function called save that accepts the context. We wrap up that method and return a tuple of a Boolean and an error, save, and check for success or error. This way, you don’t have to deal with those error pointers.

var error: NSError?
let success: Bool = managedObjectContext.save(&error)
// handle success or error

// make it Swift-er
func saveContext(context:) -> (success: Bool, error: NSError?)

// Example
let result = saveContext(context)
if !result.success {
  println("Error: \(result.error)")
}

Fetch Requests (35:23)

For fetch requests, here is the current way that you would execute a fetch request with this method on the context. Again, we see this error pointer, and because it’s bridged from Cocoa, we get the results as an optional array of AnyObject. this means that we have to cast to our actual objects.

var error: NSError?
var results = context.executeFetchRequest(request, error: &error)
// [AnyObject]?
if results == nil {
  println("Error = \(error)")
}

To improve this so that we can actually subclass fetch requests, add a generic parameter. Then we can have a top-level, generic function that accepts a type T for the FetchRequest.

// T is a phantom type
class FetchRequest <T: NSManagedObject>: NSFetchRequest {
  init(entity: NSEntityDescription) {
    super.init()
    self.entity = entity
  }
}

typealias FetchResult = (success: Bool, objects: [T], error: NSError?)
func fetch <T> (request: FetchRequest<T>,
                context: NSManagedObjectContext) -> FetchResult {
    var error: NSError?
    if let results = context.executeFetchRequest(request, error: &error) {
      return (true, results as! [T], error)
    }
      return (false, [], error)
}

In operation, it would look like this. Going back to our music analogy, we create a request for a band. We can fetch that request, get the results, and then use them. The objects that we get are typed, and we get an array of the type that we passed in.

// Example
let request = FetchRequest<Band>(entity: entityDescription)
let results = fetch(request: request, inContext: context)

if !results.success {
  println("Error = \(results.error)")
}

results.objects // [Band]

If you really value Objective-C’s dynamism, then we’ve actually lost a little bit by using Swift. There are popular libraries like Mantle that simplify a lot of this parsing and persistence to Core Data, but it relies heavily on the Objective-C runtime and its dynamic nature.

Recap & Q&A (40:29)

To review, one of our goals was clarity. We can get this with optionals, enums, or typealias, as well as designated initializers, where we can clearly see default values and optional values for our models. With the designated initializer, we’re not just creating an object without initializing all of its properties. We get these Swift-specific features like enums and optionals, and we get these functional aspects for saving and fetching. There’s a lot more that we can actually do with this.

To see a framework where I’ve started a lot of this, see my GitHub here.

Q: When would you be comfortable using Swift for production code?
Jesse: There’s a lot of articles online, but I haven’t used Swift in production yet. I think Apple is improving it very rapidly and there are lots of benefits to using Swift in production. Probably one of the most detrimental aspects to using Swift are the tooling issues, which have a toll on your productivity. As far as what we’ve covered in this talk, I think Swift is a huge win, especially with designated intializers.

Q: Are any of these techniques applicable to a legacy app written in Core Data? Would it be appropriate to apply Swift in this code base?
Jesse: There isn’t really a reason to change your entire code base and go through these drastic changes. It depends on the situation that you’re in, and what you’re really hoping to gain from changing, because it will take a lot to switch over to Swift from an old, Objective-C code base with Core Data. The other thing is that a lot of these features discussed don’t bridge to Objective-C. So if you’re only switching over your model, you’d have issues in other parts of your code base where you can’t access these things. You would have to do lots of work-arounds to make that work.



Jesse Squires

Jesse Squires

Jesse is an iOS developer at Instagram who writes about Swift and Objective‑C on his blog at jessesquires.com. He's the curator of the Swift Weekly Brief newsletter and co-host of the Swift Unwrapped podcast. He is fueled primarily by black coffee and black metal.