(UPDATE: Updated code to implement some of Wain's suggestions below.)
I have been fighting and fighting with the following: I have an app - in Swift - that connects to an API with Restkit to pull down events as they are added/updated. The following is an example of some of the implementation.
API JSON:
[
{
id: 7,
title: "Event 1",
description: "Lorem ipsum dolor...",
},
{
id: 8,
title: "Event 2",
description: "Lorem ipsum dolor...",
}
]
AppDelegate:
// Setting up object manager
let baseURL = NSURL(string: "http://url.com")
var objectManager = RKObjectManager(baseURL: baseURL)
objectManager.managedObjectStore = managedObjectStore
// Initialize managed object model and store
var managedObjectModel = NSManagedObjectModel.mergedModelFromBundles(nil)
managedObjectStore = RKManagedObjectStore(managedObjectModel: managedObjectModel)
objectManager.managedObjectStore = managedObjectStore
// Complete CoreData stack initialization
managedObjectStore.createPersistentStoreCoordinator()
let storePath = RKApplicationDataDirectory().stringByAppendingPathComponent("MyApp.sqlite")
let seedPath = NSBundle.mainBundle().pathForResource("RKSeedDatabase", ofType: "sqlite")
var error:NSError?
var persistantStore = managedObjectStore.addSQLitePersistentStoreAtPath(storePath, fromSeedDatabaseAtPath: seedPath, withConfiguration: nil, options: nil, error: nil)
// Creating the managed object contexts
managedObjectStore.createManagedObjectContexts()
// Configure a managed object cache to ensure we don't create duplicate objects
managedObjectStore.managedObjectCache = RKInMemoryManagedObjectCache(managedObjectContext: managedObjectStore.persistentStoreManagedObjectContext)
// Mapping events to Restkit
var eventsMapping = RKEntityMapping(forEntityForName: "Event", inManagedObjectStore: managedObjectStore)
eventsMapping.identificationAttributes = ["eventID", "name", "eventDescription"]
eventsMapping.addAttributeMappingsFromDictionary([
"id":"eventID",
"title":"name",
"description":"eventDescription"
])
// Response descriptor for Events
let eventsDescriptor = RKResponseDescriptor(mapping: eventsMapping, method: RKRequestMethod.GET, pathPattern: "/api/v1/events", keyPath: nil, statusCodes: NSIndexSet(index:200))
// Registering mappings with object manager
objectManager.addResponseDescriptorsFromArray([eventsDescriptor])
An update method in a database model:
class func runUpdates(completion:(updated:Bool) -> Void) {
RKObjectManager.sharedManager().getObjectsAtPath("/api/v1/events", parameters: nil,
success:{ operation, mappingResult in
completion(updated:true)
},
failure:{ operation, error in
completion(updated:false)
}
)
}
So far, this actually works pretty good: Restkit pulls the events from the API and saves them into Core Data, it's good about updating changed events, etc. The only thing is does NOT do is delete orphaned events, so if an event is deleted on the server it remains on my local data. No good.
So after Googling and Googling and reading other SO threads, the first thought was that apparently Restkit won't delete orphans if it is a POST request. Only I'm definitely making a GET request here as seen in the eventDescriptor.
The second thing I came across is that apparently orphans will only be deleted with an addFetchRequestBlock, so apparently I shouldn't use getObjectsAtPath. Examples of this in Swift is very sparse and lacking, but based on the Objective C examples I came up with this to replace the getObjectsAtPath.
RKObjectManager.sharedManager().addFetchRequestBlock { (url:NSURL!) -> NSFetchRequest! in
var pathMatcher = RKPathMatcher(pattern: "/api/v1/events")
var match = pathMatcher.matchesPath(url.relativePath, tokenizeQueryStrings: false, parsedArguments: nil)
if(match) {
var fetchRequest = NSFetchRequest(entityName: "Event")
return fetchRequest
} else {
return nil
}
}
However, it doesn't work at all: it automatically returns nil (and even ignores any break threads around the pathMatcher prior to checking the pathMatcher to try and see what it's doing).
Clearly, I'm overlooking something or not writing the method in the correct way / doing something wrong, any ideas on what's up?
You are a little confused about the purpose of the fetch request block. It is there to finds objects which can be deleted, but it has nothing to do with downloading. So, you configure the block when you configure your mapping, and then it swings into action after your GET is complete to tidy up old (orphan) items.
As an aside you should really be using identificationAttributes
on your mapping and creating a RKInMemoryManagedObjectCache
to use as the store managedObjectCache
. This allows proper matching of existing items when new content is received.