davidethell
davidethell

Reputation: 12038

RestKit, CoreData and Swift - I can't seem to fetch results back out

I have successfully set up a RestKit to CoreData mapping implementation in a new Swift-based app (currently XCode 6 beta 3). I know the import mappings are working from my RestKit JSON call because I can inspect the sqlite database and see my data. I am unable, however, to get the data back out of the data store in a NSFetchRequest. What am I doing wrong?

I'll spare all the RestKit setup and teardown because it's pretty standard and appears to be working great. So here is my AppDelegate query code that doesn't seem to be working:

var currentUser: User? {
    if !_currentUser {
        var error: NSError? = nil
        let request = NSFetchRequest(entityName: "User")
        let recordCount = self.managedObjectContext.countForFetchRequest(request, error:&error)
        NSLog("user records found: \(recordCount)")
        var result = self.managedObjectContext.executeFetchRequest(request, error:&error)
        for resultItem : AnyObject in result {
            _currentUser = resultItem as? User
            if _currentUser {
                NSLog("Fetched User for \(_currentUser!.firstname) \(_currentUser!.lastname)")
            }
        }
    }
    return _currentUser;
}

self.managedObjectContext refers to this from my AppDelegate to get the context from the RestKit shareObject:

var managedObjectContext: NSManagedObjectContext {
    return RKObjectManager.sharedManager().managedObjectStore.mainQueueManagedObjectContext
}

It appears that the fetch request is successful because a breakpoint in the for/in loop occurs. When I inspect the resultItem or _currentUser objects, however, they appear empty and the "if _currentUser" NSLog never fires.

Any ideas? Am I making incorrect assumptions about getting data back out in Swift?

EDIT 2:

The problem stems from my attempt to case the resultItem into an Optional. If declare _currentUser without the optional and remove the as? optional cast the query returns a proper User object:

        for resultItem : AnyObject in result {
            _currentUser = resultItem as User
            NSLog("Fetched User for \(_currentUser.firstname) \(_currentUser.lastname)")
        }

EDIT: I added a record count before the main fetch request and it properly shows 1 record. So something is wrong with how I'm trying to map the fetch result into my user object. Here is my user class:

import Foundation
import CoreData

class User: NSManagedObject {
    @NSManaged
    var id: Int32
    @NSManaged
    var createdAt: NSDate
    @NSManaged
    var udpatedAt: NSDate
    @NSManaged
    var username: String
    @NSManaged
    var email: String
    @NSManaged
    var firstname: String
    @NSManaged
    var lastname: String
    @NSManaged
    var organization: String
    @NSManaged
    var tokens: NSArray
}

Upvotes: 4

Views: 9132

Answers (1)

davidethell
davidethell

Reputation: 12038

The answer is that apparently Swift does not like casting the fetch result as an optional. I have to put the result into a local variable and then set the optional:

var currentUser: User? {
    if !_currentUser {
        var error: NSError? = nil
        let request = NSFetchRequest(entityName: "User")
        let recordCount = self.managedObjectContext.countForFetchRequest(request, error:&error)
        NSLog("user records found: \(recordCount)")
        var result = self.managedObjectContext.executeFetchRequest(request, error:&error)
        for resultItem : AnyObject in result {
            var currentUserItem = resultItem as User
            NSLog("Fetched User for \(currentUserItem.firstname) \(currentUserItem.lastname)")
            _currentUser = currentUserItem
        }
    }
    return _currentUser;
}

Here is my setup and teardown of RestKit in Swift in case anyone (like niiamon) finds it helpful:

From my RestApi.swift:

var objectStore: RKManagedObjectStore = RKManagedObjectStore()

init() {
    configureRestKit()
}

func configureRestKit() {
    let objectManager = RKObjectManager(baseURL: NSURL.URLWithString(baseUrl))
    //objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    RKObjectManager.setSharedManager(objectManager)

    objectStore = RKManagedObjectStore(managedObjectModel: managedObjectModel())
    let dataPath = "\(RKApplicationDataDirectory())/MyApp.sqlite"
    NSLog("Setting up store at \(dataPath)")
    objectStore.addSQLitePersistentStoreAtPath(dataPath, fromSeedDatabaseAtPath: nil, withConfiguration: nil, options: optionsForSqliteStore(), error: nil)
    objectStore.createManagedObjectContexts()
    objectStore.managedObjectCache = RKInMemoryManagedObjectCache(managedObjectContext: objectStore.persistentStoreManagedObjectContext)
    objectManager.managedObjectStore = objectStore

    // -- Declare routes -- //

    // Login Route
    objectManager.addResponseDescriptor(userLoginResponseDescriptor())
    objectManager.addResponseDescriptor(eventLoginResponseDescriptor())
    objectManager.router.routeSet.addRoute(RKRoute(name:kUserLoginRouteName, pathPattern: "/login", method: RKRequestMethod.POST))
}

func tearDownRestKit() {
    // Cancel any network operations and clear the cache
    RKObjectManager.sharedManager().operationQueue.cancelAllOperations()
    NSURLCache.sharedURLCache().removeAllCachedResponses()

    // Cancel any object mapping in the response mapping queue
    RKObjectRequestOperation.responseMappingQueue().cancelAllOperations()

    // Ensure the existing defaultStore is shut down
    NSNotificationCenter.defaultCenter().removeObserver(RKManagedObjectStore.defaultStore())

    RKObjectManager.setSharedManager(nil)
    RKManagedObjectStore.setDefaultStore(nil)
}

func userMapping() -> RKEntityMapping {
    let userMapping = RKEntityMapping(forEntityForName: "User", inManagedObjectStore: objectStore)

    var userDictionary = ["id": "id", "created_at": "createdAt", "updated_at": "updatedAt", "username": "username", "email": "email", "firstname": "firstname", "lastname": "lastname", "organization": "organization"]

    userMapping.addAttributeMappingsFromDictionary(userDictionary)
    let tokenMapping = RKEntityMapping(forEntityForName: "ApiToken", inManagedObjectStore: objectStore)
    tokenMapping.addAttributeMappingsFromArray(["token", "expiration"])
    userMapping.addRelationshipMappingWithSourceKeyPath("tokens", mapping:tokenMapping)
    return userMapping
}

func userLoginResponseDescriptor() -> RKResponseDescriptor {
    let userResponseDescriptor = RKResponseDescriptor(mapping: userMapping(), method: RKRequestMethod.POST, pathPattern: "/login", keyPath: "user", statusCodes: NSIndexSet(index: 200))
    return userResponseDescriptor
}

func managedObjectModel() -> NSManagedObjectModel {
    return NSManagedObjectModel.mergedModelFromBundles(nil)
}

func optionsForSqliteStore() -> NSDictionary {
    return [
        NSInferMappingModelAutomaticallyOption: true,
        NSMigratePersistentStoresAutomaticallyOption: true
    ];
}

Upvotes: 6

Related Questions