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 Source Code Creating the Model First, we'll create a new CoreData model called Episodes.xcdatamodel. then we'll define our properties: 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.