I'm new to RestKit so this question may be dumb: When using [objectManager sendObject:...], how do I tell RestKit which mapping it should use for the result? More specific: I am sending GET and POST data to a server which in return responds with a JSON encoded message:
{"purchaseresult":{"status":"ok","errormsg":""}}
The Objective-C code I use looks like this:
RKObjectMapping *purchaseResultMapping = [RKObjectMapping mappingForClass:[PurchaseResult class]];
[purchaseResultMapping mapKeyPathsToAttributes:#"status", #"status", #"errormsg", #"errorMessage",nil];
[objectManager.mappingProvider setMapping:purchaseResultMapping forKeyPath:#"purchaseresult"];
[[RKObjectManager sharedManager].mappingProvider setErrorMapping:purchaseResultMapping];
[objectManager sendObject:queryParams toResourcePath:#"/purchase/post" usingBlock:^(RKObjectLoader* loader) {
loader.method = RKRequestMethodPOST;
loader.resourcePath = #"/purchase/post";
loader.params = queryParams;
loader.objectMapping = purchaseResultMapping;
}];
This returns an RestKit error:
restkit.network:RKObjectLoader.m:216 Encountered errors during mapping: Expected an object mapping for class of type '__NSDictionaryI', provider returned one for 'PurchaseResult'
Any ideas what I am doing wrong here?
Thanks
Christian
The object that you're sending is a NSDictionary so RestKit is looking for an object mapping for an NSDictionary but you are providing a PurchaseResult mapping. If you send an instance of PurchaseResult or provide your mapping for the NSDictionary class then this error should be avoided.
An alternative to this would be as follows:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/purchase/post" usingBlock:^(RKObjectLoader *loader) {
loader.delegate = **Your loader delegate here**;
loader.method = RKRequestMethodPOST;
loader.params = queryParams;
}];
Just make sure you've defined your mapping for the objects coming back in and you should be good.
Related
I have a project set up where all data coming from the Server is wrote to a Core Data managed store using a managed model. I have all my entities generated from the Core Data model using mogenerator. I have all RestKit mapping integrated in to my entities.
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"dataModel" ofType:#"momd"]];
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
// Initialize the Core Data stack
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
Now there has been a change of plan due to time constraints. The data should not be stored at all. The data should be read from the server and displayed directly. No saving, no persisting. So I would like to cut out the RKManagedObjectStore, keep the entities and mappings, and read the data from 'RKMappingResult *mappingResult' when a request succeeds or a RKPaginator resutl. Example that works with RKManagedObjectStore and RKPaginator:
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[Friend entityMapping:objectManager.managedObjectStore]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"items"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
[objectManager setPaginationMapping:[self paginationMapping]];
self.paginator = [objectManager paginatorWithPathPattern:#"data"];
self.paginator.perPage = 20;
//Set completion block for this paginator
[self.paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
[weakSelf.dataArray addObjectsFromArray:objects];
} failure:^(RKPaginator *paginator, NSError *error) {
}];
However, when I start to reomve the RKManagedObjectStore I start to run into problems when mapping.
'You must provide a managedObjectStore. Invoke mappingForClass:inManagedObjectStore: instead.'
Q.1 Can I use Enitiy Mapping without RKManagedObjectStore? Am I going in the right direction.
Q.2 Can I remove the store and keep the model?
Any tips, help or examples would be great before I get too involved and go in the wrong direction.
Thanks Al
You should fight against the requirement change and use Core Data as a temporary cache of information to aid with memory management (so you can scroll up and down lists without having to have everything loaded all the time). This should not take any longer to implement...
No, you can't use RKEntityMapping without an RKManagedObjectStore.
You could keep the model but you wouldn't be able to use it (managed objects need to be created in association with a MOC).
I am trying to migrate to RestKit 0.20-pre2.
Currently I managed to migrate my mapping (at least the compiler does not complain anymore), but I have problems in creating requests (previously I used the RKObjectLoader which does not exist anymore.
My previous code is the following:
- (RKObjectLoader*)objectLoaderWithResourcePath: (NSString*)resourcePath
method: (RKRequestMethod)httpMethod
parameters: (NSDictionary*)parameters
mappableClass: (Class)objectClass
{
RKObjectMapping *mapping = [self.objectManager.mappingProvider objectMappingForClass:objectClass];
NSString *path = resourcePath;
if (httpMethod == RKRequestMethodGET) {
path = [resourcePath stringByAppendingQueryParameters:parameters];
}
RKObjectLoader *request = [self.objectManager loaderWithResourcePath:path];
request.method = httpMethod;
request.delegate = self;
request.objectMapping = mapping;
if (httpMethod != RKRequestMethodGET) {
request.params = parameters;
}
return request;
}
I used the above method to create a generic request, and then send it either synchronously or asynchronously.
Now... I saw the new method getObjectsAtPath: parameters: success: failure:, but.. I need the same for the POST (and I don't have any object to post... it is simply the server which accept a POST request for the login..)
Any help?
Thank you
I had the same problem as you and i received a great answer here:
Trying to make a POST request with RestKit and map the response to Core Data
Basically,
This is what you need:
NSDictionary *dictionary = #{ #"firstParam": #(12345), #"secondParam": #"whatever"};
NSMutableURLRequest *request = [objectManager requestWithObject:nil method:RKRequestMethodPOST path:#"/whatever" parameters:parameters];
RKObjectRequestOperation *operation = [objectManager objectRequestOperationWithRequest:request ^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Loading mapping result: %#", result);
} failure:nil];
There is an example here in README section Managed Object Request that will help you:
https://github.com/RestKit/RestKit
You can use AFNetworking directly using the RK HTTPClient subclass, something like this:
[[RKObjectManager sharedManager].HTTPClient postPath:#"/auth" parameters:params success:^(AFHTTPRequestOperation *operation, id JSON)
{
// Success
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// Error
}];
Since RestKit v0.20.x, RK now use AFNetworking under the hood instead of RKClient, so you can refer directly to the AFNetworking docs:
http://afnetworking.github.com/AFNetworking/Classes/AFHTTPClient.html#//api/name/postPath:parameters:success:failure:
Edit
In my project, for the auth, I simply created an NSObject named User, with a singleton, and managed the mapping myself. I (personally) didn't need to have my auth user in my core data stack. If you need to use the RK Core data mapping capabilities, take a look at RKObjectManager with the postObject:path:parameters:success:failure: method.
Apologies if this has been answered before but I can't find a reference. I am trying Cocoa / obj-c for the first time. I am trying to knock up an app which will sync with a remote backup system via http (a la s3) and am stumbling around some fundamental core data issues.
I have created an entity and can invoke this without issues. The problem arrives when I call save on NSManagedObjectContext.
I am not going to include all methods involved in invoking the object context / model as the log output should (I think) verify that it is working as expected.
Best described with code and appropriate log entries.
*First, for illustration, I am invoking the managed object: *
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSLog(#"The managed object model is defined as follows:\n%#", managedObjectModel);
return managedObjectModel;
}
And the log output from the above NSLog:
2011-09-06 14:31:38.322 TryAgain[18885:a0f] The managed object model is defined as follows:
(<NSManagedObjectModel: 0x2000e2b00>) isEditable 1, entities {
BackupItinerary = "(<NSEntityDescription: 0x20020e9e0>) name BackupItinerary, managedObjectClassName NSManagedObject, renamingIdentifier
BackupItinerary, isAbstract 0, superentity name (null), properties {\n \"file_url\" = \"(<NSAttributeDescription: 0x2000faec0>), name
file_url, isOptional 0, isTransient 0, entity BackupItinerary, renamingIdentifier file_url, validation predicates (\\n), warnings (\\n),
versionHashModifier (null), attributeType 700 , attributeValueClassName NSString, defaultValue (null)\";\n \"last_sync_date\" =
\"(<NSAttributeDescription: 0x2000faf60>), name last_sync_date, isOptional 1, isTransient 0, entity BackupItinerary, renamingIdentifier
last_sync_date, validation predicates (\\n), warnings (\\n), versionHashModifier (null), attributeType 900 , attributeValueClassName
NSDate, defaultValue (null)\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null)";
}, fetch request templates {
}
This looks so have been successful. No exceptions thrown, or warnings.
Now, the actual problem arrives when I call save on the object context. I have a NSOpenPanel which allows for picking dir / files to backup (all hooked up and working fine). Upon the user selecting a dir/file I want to set the value, so I:
NSArray *paths = [panel URLs];
NSURL *filePath = [paths objectAtIndex:0];
[directories addObject:filePath];
[directories setArray:[[NSSet setWithArray: directories] allObjects]];
NSEntityDescription *BackupItineraryEntity = [[self.managedObjectModel entitiesByName] objectForKey:#"BackupItinerary"];
NSManagedObject* BackupItinerary = [[NSManagedObject alloc]
initWithEntity:BackupItineraryEntity
insertIntoManagedObjectContext:self.managedObjectContext];
[BackupItinerary setValue:[filePath absoluteString] forKey:#"file_url"];
NSLog(#"entity:\n%#", BackupItinerary);
And the call to NSLog says (having selected /Users/rick/selenium2-webdriver/):
2011-09-06 14:31:38.328 TryAgain[18885:a0f] entity:
<NSManagedObject: 0x200216c80> (entity: BackupItinerary; id: 0x200090860 <x-coredata:///BackupItinerary/t0C005B39-D185-454B-B364-31314EEB10F02> ;
data: {
"file_url" = "file://localhost/Users/rick/selenium2-webdriver/";
"last_sync_date" = nil;
})
So, the file_url seems to be populated then, yes? But when I:
NSError *error;
if (![[self managedObjectContext] save:&error]) {
NSLog(#"Unresolved error %#, %#, %#", error, [error userInfo],[error localizedDescription]);
}
The log says:
2011-09-06 14:31:38.330 TryAgain[18885:a0f] Unresolved error Error Domain=NSCocoaErrorDomain Code=1570 UserInfo=0x2000cc4e0 "file_url is a required
value.", {
NSLocalizedDescription = "file_url is a required value.";
NSValidationErrorKey = "file_url";
NSValidationErrorObject = "<NSManagedObject: 0x20020f660> (entity: BackupItinerary; id: 0x20008faa0 <x-coredata:///BackupItinerary/t0C005B39
D185-454B-B364-31314EEB10F03> ; data: {\n \"file_url\" = nil;\n \"last_sync_date\" = nil;\n})";
}, file_url is a required value.
So basically:
I can, it seems (?), invoke the an entity and set a value on it, but when it comes to saving it the values is not set. The above code is executed inline and I am using garbage collection.
Feels like a total newb school boy error issue but for the life of me I can't see what I am missing having gone over the docs, highlevel tutorials and example code.
Pointers appreciated!
You are looking at two different managed object instances. The instance that has its file_url property set is <NSManagedObject: 0x200216c80> while the one that reports the error is <NSManagedObject: 0x20020f660>.
Somewhere, you are inserting an instance but not giving it a file_url value. In the code given, it might happen if the user selects cancel in the NSOpenPanel.
Judging from the output of your NSLog's, I'd say that BackupItinerary object is different when saving your context. Check this output on different occasions:
When inserting and setting the value:
entity:
<NSManagedObject: 0x200216c80>
When saving:
NSValidationErrorObject = "<NSManagedObject: 0x20020f660>
As you can see the addresses of instances differ, hence you're inserting one, but saving the other instance. Hope this helps.
I'm having a really confounding problem using Core Data. In my Core Data store, for an existing Core Data object, I'm checking whether a relationship exists, and if not, I create the object like so (this is a method on AFFingerprintGeneratorOperation):
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
[newContext setMergePolicy:NSOverwriteMergePolicy];
NSPersistentStoreCoordinator *sharedStoreCoordinator = [[AFMainController sharedInstance] persistentStoreCoordinator];
[newContext setPersistentStoreCoordinator:sharedStoreCoordinator];
[self setManagedObjectContext:newContext]; // retaining property
[newContext release];
NSEntityDescription *fetchedTagSetEntity = [NSEntityDescription entityForName:#"FetchedTagSet"
inManagedObjectContext:newContext
];
AFTrack *retrievedTrack = (AFTrack *)[newContext objectWithID:[self trackObjectID]];
[self setTrack:retrievedTrack];
if (! retrievedTrack.fetchedTagSet) {
AFFetchedTagSet *newFetchedTagSet = [[AFFetchedTagSet alloc] initWithEntity:fetchedTagSetEntity
insertIntoManagedObjectContext:newContext];
[[retrievedTrack storedTrack] setFetchedTagSet:newFetchedTagSet];
[newFetchedTagSet setStoredTrack:[retrievedTrack storedTrack]];
}
AFTrack, AFFetchedTagSet, and AFStoredTrack are all Core Data objects. AFFetchedTagSet and AFStoredTrack are in an on-disk Core Data store, while AFTrack is in a separate in-memory Core Data store.
Note that since the AFStoredTrack object is in a separate store, I need to fetch it like so (this is a method on AFTrack):
NSManagedObjectContext *objectContext = [self managedObjectContext];
NSPersistentStoreCoordinator *coordinator = [objectContext persistentStoreCoordinator];
NSString *URIString = [self storedTrackObjectIDString];
AFStoredTrack *theStoredTrack = nil;
if (URIString) {
NSURL *objectURL = [NSURL URLWithString:URIString];
NSManagedObjectID *storedTrackObjectID = [coordinator managedObjectIDForURIRepresentation:objectURL];
theStoredTrack = (AFStoredTrack *)[objectContext objectWithID:storedTrackObjectID];
}
return theStoredTrack;
Since this is a method on AFTrack, it simply retrieves the AFTrack's own managed object context, so the code in the first excerpt should always use the exact same managed object context for all operations.
However, in the first excerpt, after calling setFetchedTagSet: with the new object, and attempting to save the Core Data store, I get this error:
"Dangling reference to an invalid object." = "<null>";
NSAffectedObjectsErrorKey = (
"<AFStoredTrack: 0x11550eef0> (entity: StoredTrack; id: 0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121> ; data: {\n fetchedTagSet = \"0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124>\";\n fingerprint = \"ASPtPiNHPC7fGSYXTxtfFboMCg7BCxYQ+gZRCL4FWQdzBD8HPw\";\n persistentID = nil;\n status = 3;\n updateAlbumName = nil;\n updateArtistName = nil;\n updateArtwork = nil;\n updateGenre = nil;\n updateLyrics = nil;\n updateReleaseYear = nil;\n updateTrackName = nil;\n})"
);
NSLocalizedDescription = "storedTrack is not valid.";
NSValidationErrorKey = storedTrack;
NSValidationErrorObject = "<AFFetchedTagSet: 0x11c956ac0> (entity: FetchedTagSet; id: 0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124> ; data: {\n ampliFindPUID = nil;\n fingerprint = nil;\n matchAlbum = nil;\n matchLyrics = nil;\n matchTrackName = nil;\n matchTrackNumber = 0;\n storedTrack = \"0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121>\";\n})";
NSValidationErrorValue = "<AFStoredTrack: 0x11550eef0> (entity: StoredTrack; id: 0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121> ; data: {\n fetchedTagSet = \"0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124>\";\n fingerprint = \"ASPtPiNHPC7fGSYXTxtfFboMCg7BCxYQ+gZRCL4FWQdzBD8HPw\";\n persistentID = nil;\n status = 3;\n updateAlbumName = nil;\n updateArtistName = nil;\n updateArtwork = nil;\n updateGenre = nil;\n updateLyrics = nil;\n updateReleaseYear = nil;\n updateTrackName = nil;\n})";
But both the AFFetchedTagSet and AFStoredTrack objects seem to be valid, as their ids match up, and the object context for these objects still exists and is retained by the AFFingerprintGeneratorOperation object.
I've seen this CoreData: "Dangling reference to an invalid object." error and this http://lists.apple.com/archives/cocoa-dev/2009/Nov/msg00190.html , but neither link seems to help. The former seems to say that a relationship is bad (which I don't think it is), and the latter says to avoid changing relationships in awakeFromFetch, which as far as I can tell I'm not doing.
Any help?
The error code that Core Data was throwing was actually quite correct in this instance, but it was hard to figure out what was going on.
The "fetchedTagSet" property on AFTrack objects was simply a convenience read-only property for the "fetchedTagSet" property on AFStoredTrack, meaning that in order to retrieve the AFFetchedTagSet class, I had to retrieve the AFStoredTrack object first.
The method on AFTrack that was fetching the AFStoredTrack method was using the objectRegisteredForID: method. This doesn't fetch objects if they aren't registered with the managed object context that you're calling the objectRegisteredForID: method on. So, I was retrieving non-existent AFStoredTrack faults, and setting properties on them.
(Note: "aren't registered with the managed object context" does not mean that they don't exist in the Core Data store. It simply means that that specific managed object context does not know about that object yet, because you haven't inserted it or fetched it within that context.)
For some reason, objectRegisteredForID: was returning a blank AFStoredTrack, rather than nil as the documentation states. The effect was that I thought I had a valid AFStoredTrack object, but in reality it was just a placeholder with all the values being nil. Trying to save the object made the context realize that the AFStoredTrack was not valid, producing the error message.
The fix was to change the method on AFTrack (the one that retrieved the AFStoredTrack objects) to use objectWithID: rather than objectRegisteredForID: . As the documentation states, objectWithID: will fetch an object if the context doesn't know about it yet. That returned a valid object, and then everything went fine after that.
My problem was solved using this code:
[[CustomManagedObject managedObjectContext] performBlockAndWait:^{
NSError *error;
if (![[CustomManagedObject managedObjectContext] save:&error])
{
NSLog(#"Error in Saving: %#", [error.userInfo description]);
}
}];
Since you're dealing with objects split across two different stores here, and relationships are not allow to span across different stores, you might need to make sure that the AFFetchedTagSet you're creating is assigned to the same persistent store as the AFStoredTrack object you're associating it with. I'm not sure which store Core Data defaults to when creating a new object, but I could see Core Data throwing a fit when you try to save a context that has a relationship set up that spans across the two stores, so setting the store explicitly surely couldn't hurt. You can do this by calling -[NSManagedObjectContext assignObject:toPersistentStore:] after creating the fetched tag set. Not 100% sure that's the problem, but that's what I'd try first.
As a side note, if you have inverse relationships set up in your Core Data model between AFFetchTagSet and AFStoredTrack, then only one of the two calls from -setFetchedTagSet: and -setStoredTrack: should be necessary, and Core Data should take care of the other one automagically.
I found this wonderful NSManagedObjectID. This would be very good for referencing an Entity/NSManagedObject/NSEntityDescription, right?
Let's get an ID from an entity:
NSEntityDescription *entity = [self newEntity];
NSManagedObjectID *objID = [entity objectID];
So... any idea how to get this objID into a string? Or better: NSData. Actually something to be able to save it to the NSUserDefaults. ;-)
Btw: NSFetchRequest doesn't want to work in my case. I use an modified version of this example: answer of an old question.
To get an archived URI corresponding to a NSManagedObject's objectID:
NSManagedObject* myMO;
...
NSURL *uri = [[myMO objectID] URIRepresentation];
NSData *uriData = [NSKeyedArchiver archivedDataWithRootObject:uri];
In order to get back to an instance of the original managed object, you need a CoreData stack with the persistent store holding that instance already added to the NSPersistentStoreCoordinator. Then:
NSData *uriData;
NSPersistentStoreCoordinator *psc;
NSManagedObjectContext *moc; //with moc.persistentStoreCoordinator = psc.
...
NSURL *uri = [NSKeyedUnarchiver unarchiveObjectWithData:uriData];
NSManagedObjectID *moID = [psc managedObjectIDForURIRepresentation:uri];
NSManagedObject *myMO = [moc objectWithID:moID];
From the NSManagedObjectID documentation:
Object IDs can be transformed into a
URI representation which can be
archived and recreated later to refer
back to a given object (using
managedObjectIDForURIRepresentation:
(NSPersistentStoreCoordinator) and
objectWithID:
(NSManagedObjectContext). For example,
the last selected group in an
application could be stored in the
user defaults through the group
object’s ID. You can also use object
ID URI representations to store “weak”
relationships across persistent stores
(where no hard join is possible).
Just turn it into a URL then turn that into a string or a data.
Did you look at URIRepresentation? It's easy to convert an NSURL to an NSString, and that to an NSData.
You don't need to convert the NSURL into an NSString before archiving. Just archive the NSURL.
Edit: I've recently learned that an object's ID can change, such as after a migration. It therefore seems like not a good idea to save an ID to disk expecting to be able to reference the object later.
Here's the cleanest and shortest way I've found to do this currently, using the setURL and getURL methods added in 4.0 to avoid extra calls to NSKeyedUnarchiver and NSKeyedArchiver:
Setter:
+ (void)storeSomeObjectId:(NSManagedObjectID *)objectId
{
[[NSUserDefaults standardUserDefaults] setURL:[objectId URIRepresentation]
forKey:#"someObjectIdKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Getter:
+ (SomeManagedObject *)getObjectByStoredId
{
NSURL *uri = [[NSUserDefaults standardUserDefaults] URLForKey:#"someObjectIdKey"];
NSManagedObjectID *objectId = [self.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
SomeManagedObject *object = [self.managedObjectContext objectWithID:objectId];
}
As #preston said, don't save an objectID to disk, instead:
Make a new attribute on your entity called "id"
Make a new attribute on your entitys parent entity called "myEntitysMaxId"
Override your entitys parent implementation "addNewMyEntityObject:"
There, increase "myEntitysMaxId" and set that value as the new entitys "id"
Do as you normally do when you fetch an entity based on its attributes!
Much cleaner and better!