Episode #218

Core Data with Swift

14 minutes
Published on April 28, 2016

This video is only available to subscribers. Get access to this video and 584 others.

In this episode we continue our work on the Apple TV app for NSScreencast by adding a local cache of data we receive from the API. Doing so will allow us to have content immediately on launch without waiting for the network, and will also support client-side searching and filtering. Here we talk about how to set up Core Data with Swift and write a few quick tests using an in-memory store to verify that things are working.

Episode Links

Creating the Model

First, we'll create a new CoreData model called Episodes.xcdatamodel. then we'll define our properties:


Core Data properties for the Episode model

We used Xcode's Generate NSManagedObject Subclass feature to generate our model object.

import Foundation
import CoreData

extension EpisodeModel {

    @NSManaged var dominantColor: String
    @NSManaged var duration: Int32
    @NSManaged var episodeDescription: String
    @NSManaged var episodeNumber: Int16
    @NSManaged var episodeTypeValue: String
    @NSManaged var hasHLS: Bool
    @NSManaged var hasWatched: Bool
    @NSManaged var hlsURLValue: String
    @NSManaged var isFavorite: Bool
    @NSManaged var largeArtworkURLValue: String
    @NSManaged var mediumArtworkURLValue: String
    @NSManaged var publishedAtTimeInterval: NSTimeInterval
    @NSManaged var serverId: Int16
    @NSManaged var thumbnailURLValue: String
    @NSManaged var title: String
    @NSManaged var videoURLValue: String

}

This file could be regenerated at a later date, so it is written as an extension on our EpisodeModel class. We can then add methods & properties to our class without fear of it being overwritten later on:

import Foundation
import CoreData

@objc(EpisodeModel)
class EpisodeModel: NSManagedObject {

    class var entityName: String {
        return "Episode"
    }

    class func createInContext(context: NSManagedObjectContext) -> EpisodeModel {
        let entity = NSEntityDescription.entityForName(entityName, inManagedObjectContext: context)!
        return EpisodeModel(entity: entity, insertIntoManagedObjectContext: context)
    }

    var publishedAtDate: NSDate {
        return NSDate(timeIntervalSince1970: publishedAtTimeInterval)
    }

    var episodeType: EpisodeType {
        return EpisodeType(rawValue: episodeTypeValue)!
    }
}

Testing it out with an In-Memory Store

We want to write a test to ensure that we can save & retrieve these models from the store, so we'll use an in-memory store to keep things simple and fast.

First, we'll start with a special test case subclass that provides the setup for us:

import XCTest
import CoreData

class CoreDataTestCase: XCTestCase {

    func setupInMemoryStore() -> NSManagedObjectContext {
        let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!
        let psc = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
        do {
            try psc.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil)
        } catch {
            XCTFail()
        }

        let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        context.persistentStoreCoordinator = psc
        return context
    }

}

Then we'll write the test case for our specific models:

import XCTest
import CoreData
@testable import nsscreencast_tvdemo

class EpisodeModelTests : CoreDataTestCase {

    var context: NSManagedObjectContext!

    override func setUp() {
        super.setUp()
        self.context = setupInMemoryStore()
    }

    func testCanSaveEpisode() {
        let episode = EpisodeModel.createInContext(context)
        episode.title = "test episode"
        episode.duration = 15

        do {
            try context.save()
        } catch {
            XCTFail()
        }
    }

    func testCanFetchEpisodes() {
        let addEpisode: (NSManagedObjectContext, Int16, String) -> EpisodeModel = {
            context, episodeId, title in

            let episode = EpisodeModel.createInContext(context)
            episode.title = title
            episode.episodeNumber = episodeId
            return episode
        }

        addEpisode(context, 1, "first episode")
        addEpisode(context, 2, "second episode")
        addEpisode(context, 3, "third episode")

        do {
            try context.save()
        } catch {
            XCTFail()
        }

        let fetchRequest = NSFetchRequest(entityName: EpisodeModel.entityName)
        fetchRequest.sortDescriptors = [
            NSSortDescriptor(key: "episodeNumber", ascending: false)
        ]

        do {
            let results = try context.executeFetchRequest(fetchRequest)
            XCTAssertEqual(3, results.count)

            if let episode = results.first as? EpisodeModel {
                XCTAssertEqual(3, episode.episodeNumber)
            } else {
                XCTFail()
            }
        } catch {
            XCTFail()
        }
    }
}

This verifies that we can save and retrieve episodes, as well as provide custom sorting of the results.

This episode uses Swift 2.2, Xcode 7.