Core Data with Swift

Episode #218 | 14 minutes | published on April 28, 2016 | Uses swift-2.2, Xcode-7
Subscribers Only
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.

blog comments powered by Disqus