Reputation: 12038
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
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