mardi 5 mai 2015

How can I fetch only object rows sent&mapped back from server when using RestKit+CoreData?

I am using RestKit 0.2.x with Core Data and following the standard tutorials, ie:

  • Create Core Data model and use mogenerator to make code
  • Instantiate object manager with base URL
  • Create managed object context and persistent store
  • Create entity mappings for all entities returned by my web service
  • Create response descriptors for all web service endpoints and entities
  • Add response descriptor to object manager

Everything seems to be "working" just fine ... I can call

[[RKObjectManager sharedManager] getObjectsAtPath:_requestPath parameters:_requestParameters success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    [self requestSuccess];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    [self requestError:error];
}];

... all day long, and I then I keep handling with (as shown in the tutorials)

- (void)requestSuccess {
    NSManagedObjectContext *managedObjectContext = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:_entityName];
    fetchRequest.sortDescriptors = @[_defaultSortDescriptor];

    NSError *error = nil;
    requestData = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

    [_delegate handleRequestSuccess:self withData:requestData];
    //[self cleanupRequestBeforeSuccessWithData:requestData];
    [self completeRequest];
}

Now the problem is that at least by default, RestKit+CoreData actually persists your GET'ted objects to its own persistence store, or something like that. I'll explain the "cleanupRequest..." in a moment.

That kind of defeats the purpose of trying to allow the users to specify parameters at the level of the web service client, because all of the objects seem to end up in the same place anyway.

For instance, let's say I have a method /api/endpoint?queryString and I call it with two different sets of parameters:

[[RKObjectManager sharedManager] getObjectsAtPath:@"/api/endpoint" parameters:PARAMS_ONE success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    [self requestSuccess];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    [self requestError:error];
}];

[[RKObjectManager sharedManager] getObjectsAtPath:@"/api/endpoint" parameters:PARAMS_TWO success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    [self requestSuccess];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    [self requestError:error];
}];

If I then blindly follow the tutorials about how to retrieve my objects, my callbacks are then identical!

NSManagedObjectContext *managedObjectContext = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"EndpointDataTransferObject"];
fetchRequest.sortDescriptors = @["endpointDataTransferObjectID"];

NSError *error = nil;
requestData = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

The result, of course, is that the my delegate gets sent (pseudocode) requestData WHERE PARAMS_ONE on the first call, and then requestData WHERE PARAMS_ONE UNION requestData WHERE PARAMS_TWO on the second call.

Now all I really want is to be able to conduct the NSFetchRequest on only those items mapped from the web service. I think this is a totally reasonable expectation, so clearly I am missing something because whoever wrote this library is much more clever than I.

For instance, if I could somehow get an NSArray of all the objects from the two parameters it provides in the success block (RKRequestRequestOperation *o, RKMappingResult *m) - and if I can, please tell me how!!! - then my problem would be solved, and I could enjoy the caching without having to worry about whether my filters are being ignored.

What I do not want to do, however, is this:

  • Call getObjectsAtPath: parameters: success: failure: with parameters and/or path representing a sort of "server-side" predicate
  • On success, create a NSFetchRequest and a client-side predicate that mirrors my server-side predicate

This approach seems really really dumb, and yet, I don't know any better. But I refuse to do that. It is error-prone, redundant, and potentially resource-intensive.

So instead, I've opted to add a little method cleanupRequestBeforeSuccessWithData at the end of my success callback before calling completion:

- (void)cleanupRequestBeforeSuccessWithData:(NSArray *)managedObjects {
    NSManagedObjectContext *managedObjectContext = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;

    for (NSManagedObject *o in managedObjects) {
        [managedObjectContext deleteObject:o];
    }

    NSError *error = nil;
    [managedObjectContext save:&error];
}

This is ugly but it sure gets the job done. Now it totally empties the cache, but I'd rather have to make requests over and over again than to form "server-side" predicates with URL's and then form client-side NSPredicates.

What am I missing about how this is supposed to work? Clearly, I'm missing something big.

Aucun commentaire:

Enregistrer un commentaire