I'm attempting to use RestKit to handle posting an updated User object to my remote web service.
Currently my GET requests seem to be working fine but I'm having issues using
[[RKObjectManager sharedManager] postObject:updatedUser path:#"path" parameters:nil success:nil failure:nil];
Invoking this method is throwing a EXC_BAD_ACCESS exception.
My mappings are set up as follows, I believe I have both the RKRequestDescriptor and RKResponseDescriptor's.
User Response Mapping:
RKEntityMapping * userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([User class])
inManagedObjectStore:[manager managedObjectStore]];
….Setup mapping (I excluded a relationship mapping on this object)
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:userMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"currentUser"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]]
Request mapping:
[manager addRequestDescriptorsFromArray:#[
[RKRequestDescriptor requestDescriptorWithMapping:[userMapping inverseMapping]
objectClass:[User class]
rootKeyPath:nil
method:RKRequestMethodPOST]]];
The mappings seem to set up fine, the EXC_BAD_ACCESS exception is thrown when I call postObject
The test method looks like this, _updatedUser is a CoreData object fetched using [RKObjectManager sharedManager] getObjectsAtPath:…
-(void) doPost{
//this user is a CoreData object fetched using
[_updatedUser setBio:#"This is an update!"];
RKObjectManager * objectManager = [RKObjectManager sharedManager];
[objectManager postObject:_updatedUser
path:#"update/user"
parameters:nil
success:…
failure:…];
}
I've attempted using NSZombies to find the cause of this but I have't had much luck.
From what I can tell the start of the issue seems to be coming from RKObjectParameterization's -[RKObjectParameterization mappingOperation:didSetValue:forKeyPath:usingMapping:] where it looks like everything passed into the method is nil or an empty string.
Thanks!
Much thanks to Wain, after spending way too much time on this the error became instantly apparent after I turned on logging:
RKLogConfigureByName("RestKit", RKLogLevelWarning);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
It turns out I had a circular reference between mapped objects.
I have a one to one relationship where a User contains a UserProfile
I incorrectly set up a bidirectional relationship mapping between User and UserProfile
[userProfileMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"user"
toKeyPath:#"user"
withMapping:userMapping]];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"userProfile"
toKeyPath:#"userProfile"
withMapping:userProfileMapping]];
It looks like my endless loop was caused by userProfileMapping
Thanks Wain, logging lesson learned.
Related
I have an entity Comments. When I perform a rest operation, i get a response which has fields such as
{
"status": "succeed"
}
I want to process these fields to know if the operation was successful or not but I dont want to add status to comment class because it doesnt belogn there.
RKObjectManager *sharedRKObjectManager = [RKObjectManager sharedManager];
RKManagedObjectStore *managedObjectStore = [sharedRKObjectManager managedObjectStore];
// Create a mapping for the comment entity
RKEntityMapping *responseMapping = [RKEntityMapping mappingForEntityForName:ENTITY_COMMENT inManagedObjectStore:managedObjectStore];
[responseMapping addAttributeMappingsFromDictionary:#{
#"comment_id": #"commentId"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodAny
pathPattern:COMMENT
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Whats the best way to do this?
Just use a plain RKObjectMapping to a custom class or an NSDictionary and with the single status key. Then you should get a simple single item in the mapping result.
I'll try to explain how I want the mapping done:
TOP lvl json object contains Players object which is an array of Player objects
each Player object contains an array, I want each of those objects in the array to be of an Event object (custom object).
now since I have a mapping of the Player object and i'm getting the array filled, tho instead of Event objects(which is what I want), i'm getting NSDictionary objects. thing is that I do have a mapping of my Event class. my issue is getting the RestKit to map these into the array.
I've tried adding responseDescriptors of an Event class tho i've had no luck.
Here is the Player object mapping
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
...more here
#"activeEvents" : #"activeEvents"
}];
here is the request method
NSURL *taskURL = [NSURL URLWithString:kAppWebApiURLPath];
// Set object manager with base url
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager = [RKObjectManager managerWithBaseURL:taskURL];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager.HTTPClient setDefaultHeader:#"Authorization" value:kAppWebAPIKey];
[objectManager.HTTPClient setDefaultHeader:#"Content-Type" value:#"application/json"];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider inverseLoginMapping] objectClass:[LoginInfo class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
RKResponseDescriptor *playersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider playerMapping] method:RKRequestMethodGET pathPattern:nil keyPath:#"players" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:playersResponseDescriptor];
NSLog(#"%#",loginInfo.iOSDeviceToken);
[objectManager postObject:loginInfo path:kAppWebApiLoginPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{}...
Update, I now need one step further of mapping, My player object contains an array of events which I successfully mapped using
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:[MappingProvider eventMapping]];
yet now each of those Event objects contains an array of Players, so its like Players -> Events -> Players.
Here is the Mapping for both Event and Player objects :
RKObjectMapping* eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping addAttributeMappingsFromDictionary:#{
#"stuffhere" : #"stuffz"
}];
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"activeEvents" : #"activeEvents"
}];
[eventMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents/players" mapping:playerMapping];
now I don't get a recursive function, but how do I state in code to make that relationship
mapping of the json array to assign to my local array property ?
Remove #"activeEvents" : #"activeEvents" from the mapping and replace it with:
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:eventMapping];
You should also only have one response descriptor because the data is nested.
I can't figure out how to correct the errors I'm getting after having upgraded from RestKit 0.10 to 0.20... Can anyone help?
Thanks!
ViewController.m
- (void)viewDidLoad {
// Wain, I added this RKResponseDescriptor
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:[Feed mapping] method:RKRequestMethodAny
pathPattern:nil keyPath:#"springs" statusCodes:nil];
// These 4 lines has errors and needs to be fixed
// No visible #interface for 'RKObjectManager' declares the selector 'loader'
[[RKObjectManager sharedManager]
loadObjectsAtResourcePath:#"/springs/?apikey=xxx"
usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
springs = objects;
// These 2 lines have errors that need to be fixed
// Use of undeclared identifier 'loader'
[loader.mappingProvider setMapping:[Spring mapping] forKeyPath:#"springs"];
loader.onDidLoadResponse = ^(RKResponse *response){
}
I see the GitHub help page for this, but I can't figure this out with just that on my own. Really appreciate it!
Update
Ok I think I understand how to replace the first line, just by using
[RKObjectManager.sharedManager getObjectsAtPath:#"/springs/?apikey=xxx" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)...
But I'm still not sure what to do with the loader.onDidLoadObjects = ^(NSArray *objects) part!
The 0.1x code you show downloads the response data and then calls your callback to ask what to do with it, so you supply the mapping in the callback.
0.2x doesn't work like that. It takes all of the mappings as configuration before you make the request, and the mappings are associated with response descriptors (so RestKit can search all the config options and apply everything that matches).
So, you need to take your configuration and apply it to the RKObjectManager before you actually 'get' any content from the server.
This is my response from API:
{"is_favorited":1}
I want to map it by RestKit but I can't get it working. This I have in AppDelegate.m:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addAttributeMappingsFromDictionary:#{
#"is_favorited" : #"Is_favorited",
}];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Path is okay. I have token in address. I am calling it by:
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager getObjectsAtPath: [NSString stringWithFormat:#"/api/isPointFavorited/%#", sid]
parameters:#{
#"pid":[NSString stringWithFormat:#"%#", actualPlace.Id]
}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
...
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
...
}];
I am using RestKit several times in app and with sid (login token) too. So there is no problem with address or method call. Problem must be in mapping but I don't know how to do it. I tried to change keyPath from nil to #"is_favorited" but it didn't help.
My Is_favorite.h:
#interface IsFavorite : NSObject
#property(nonatomic) NSNumber *Is_favorited;
#end
RestKit error:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No mappable object representations were found at the key paths searched." UserInfo=0x1849cbc0 {DetailedErrors=(
), NSLocalizedFailureReason=The mapping operation was unable to find any nested object representations at the key paths searched: events, results
The representation inputted to the mapper was found to contain nested object representations at the following key paths: is_favorited
This likely indicates that you have misconfigured the key paths for your mappings., NSLocalizedDescription=No mappable object representations were found at the key paths searched., keyPath=null}
Edit - changes in AppDelegate but still not working:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:#"is_favorited"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Remove you usage of addAttributeMappingsFromDictionary:, and change to:
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
then change the keyPath on the response descriptor to #"is_favorited".
This is known as a nil key path mapping (because of the nil source key path in the mapping).
I am writing an iOS app that interacts with a service to update a user's profile. In order to update a user's name, the iOS app PUTs a request with a body of the following format:
{"name" : {"first" : "FIRSTNAMEGOESHERE", "lastName":"LASTNAMEGOESHERE"}}
In the case where the request body is valid (i.e. both a first and last name are provided), the service returns a 204 (No Content) status code and an empty response body.
In the case where the request body is invalid (e.g. first name is missing, etc.), the service will return a status code of 400 and a response body in the following format:
{"code":"ERRORCODEHERE"}
The iOS app is using RestKit and I have been unable to figure out how to get it to both properly handle the success and failure case. If I use this:
- (void)updateUserName
{
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[[NSURL alloc] initWithString:#"http://someqaserver:8080"]];
[objectManager setRequestSerializationMIMEType:RKMIMETypeJSON];
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromDictionary:#{#"firstName" : #"first", #"lastName" : #"last"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userMapping objectClass:[OurUser class] rootKeyPath:#"name"];
[objectManager addRequestDescriptor:requestDescriptor];
[objectManager putObject:[OurUser getCurrentUser]
path:#"/mobile/profile/name"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *result)
{
NSLog(#"Success!");
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
NSLog(#"Fail!");
}];
}
This obviously does not work for the error case, but it does successfully execute the success block in the case where a 204 is returned from the server.
If I add the following code before calling putObject, the error handling works, but now the valid 204 case is deemed a failure:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[ServiceError class]];
[errorMapping addAttributeMappingsFromDictionary:#{#"code" : #"errorCode"}];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorDescriptor];
This seems to happen since the accepted HTTP status codes are now set to those of the response descriptors provided and 204 is not one of them (or maybe it's something else?)
How can I get it so that:
1.) if a 204 is returned, it's seen as a success
2.) if a 4XX is returned, it's seen as a failure and the error code is properly mapped to my ServiceError object?
Thanks in advance for any help you can provide.
The problem was solved by also adding a response descriptor to handle the 204:
RKResponseDescriptor *successResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[RKObjectMapping mappingForClass:nil]
method:RKRequestMethodPUT
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:successResponseDescriptor];
code 204 is not an error, any 2xx status is deemed a success. 204 means 'no content', which is a perfectly valid response.
If you expect restkit to treat it as an error, make sure you throw a 3xx, 4xx or a 5xx error. In your case you are using a RKStatusCodeClassClientErrorm which would suggest you should throw a 4xx error.
See wikipedia for a complete list of error codes