Upgrading RestKit from 0.10 to 0.20 problems - ios

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.

Related

Restkit-Failed to call designated initializer on NSManagedObject class

I am using Restkit for a class that Extends NSManagedObject.
I am aware that rest kit itself has functionality to save to core data from network fetch. However, I cannot use that functionality due to the following reasons:
My application will be fetching data from sockets as well as from rest kit , so I would want a centralised location for saving/deleting logic.
My server does not confirm to rest protocols, so many times I have to send a POST request even when I really want to delete something in server.
So What I wanted to do was have my Model classes extend nsmanaged object, and save it when I want to. But I get this error:
CoreData: error: Failed to call designated initializer on
NSManagedObject class
Is there a way to go around this ?
I am fetching from server like this :
#implementation API_Login
+(void)performLoginWithEmail:(NSString*)email
withPassword:(NSString*)password
success:(void (^)(Token* user) )success
failure:failureblock failure{
RKObjectManager * objectManager = [APIHelper getRestObjectManager];
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[Token class]];
//add mapping for token
[tokenMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"token"]];
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:tokenMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
// add mapping for error
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
[errorMapping addAttributeMappingsFromDictionary:#{#"message":#"message",#"badRequest":#"badRequest"}];
RKResponseDescriptor *errorResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
NSDictionary *queryParams = #{#"email" : email,
#"password" : password,
};
[objectManager postObject:nil
path:#"/users/api/login"
parameters:queryParams
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
Token* token = (Token*)[mappingResult firstObject] ;
// [AppDelegateHandle setToken:token];
success(token);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSArray* e = [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey];
Error *err = (Error*)[e objectAtIndex:0];
NSLog(#"%#",[err.badRequest allValues] );
failure(operation,error);
}];
}
#end
My Token class looks like:
#interface Token : NSManagedObject
#property NSString* token;
#end
and my api response looks like :
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6XC9cL3JlbWluZGVyLmRldlwvdXNlcnNcL2FwaVwvbG9naW4iLCJpYXQiOjE0Njg1OTM3NjYsImV4cCI6MTQ2OTE5Mzc2NiwibmJmIjoxNDY4NTkzNzY2LCJqdGkiOiIxMDc3ZjBhY2ViYTFjOWZjZWNhYjkyMzYyOTA0ZmI4NSJ9.I6FHJLCCHr3EHQa8HgaDqxQMjF1HVyA5AymPjvBGDrM"
}
When I change Token to extend NSObject instead of NSManagedObject , everything works fine. What could the problem be ?
This happens because you're using RKObjectMapping instead of RKEntityMapping which is required if you're using a subclass of NSManagedObject.
You can't use a subclass of NSManagedObject if you aren't going to add it directly into a context.
If your request simply has a token then I wouldn't bother with RestKit probably, but in the general case I'd map to NSDictionary with the keys being the same as your managed object classes and then when you want to create your managed objects you can do so and 'import' the data to them with setValuesForKeysWithDictionary:.

RestKit .20 CoreData postObject EXC_BAD_ACCESS

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.

Having trouble doing a POST request with RESTKit

I'm trying to post some data (an authentication token for a website, to be specific) with RESTKit and I'm having trouble. Each time I run the method, I get this:
'NSInvalidArgumentException', reason: '`RKRequestDescriptor` objects must be initialized
with a mapping whose target class is `NSMutableDictionary`, got 'Login' (see
`[RKObjectMapping requestMapping]`)'
Also, there are many 'Incompatible pointer types' warnings in the code.
Here is my method which is really ugly and bad, and I would like some help fixing up. I do a [self postToken] inside an IBAction method when a button is pressed. I am getting variable userAuthToken from another file, where it is set using with the json response from a POST request not using RESTKit. I will eventually convert that POST to RESTKit when I know how to successfully. The method "userAuthTokenMethod" is in the same file as the "postToken" method, and it allows me to use the userAuthToken object, which is initialized in AppDelegate so it acts as a global variable.
- (void)postToken
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Login class]];
[mapping addAttributeMappingsFromDictionary:#{#"token": #"token"}];
NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *tokenResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:nil pathPattern:nil keyPath:nil statusCodes:statusCodeSet];
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:self rootKeyPath:nil method:nil];
[[RKObjectManager sharedManager] addRequestDescriptor:tokenRequestDescriptor];
RKObjectManager *tokenManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"https://websitetest.com/doctors/find"]];
[tokenManager addResponseDescriptor:tokenResponseDescriptor];
[tokenManager addRequestDescriptor:tokenRequestDescriptor];
[tokenManager postObject:[[self userAuthTokenMethod] userAuthToken] path:nil parameters:nil success:nil failure:nil];
NSURL *tokenURL = [NSString stringWithFormat:#"https://websitetest.com/doctors/find?name=%#&location=%#",nameIDTextField.text, locationTextField.text];
NSMutableURLRequest *tokenRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tokenURL]];
RKObjectManager *tokenObjectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:tokenURL]];
[tokenObjectManager.HTTPClient setDefaultHeader:#"Auth-Token" value:[[self network360Edge] userAuthToken]];
RKObjectRequestOperation *tokenOperation = [[RKObjectRequestOperation alloc] initWithRequest:tokenRequest responseDescriptors:#[tokenResponseDescriptor]];
NSLog(#"Token being POSTed ==> %#", [[self userAuthTokenMethod] userAuthToken]);
[tokenOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result){
NSLog(#"Response for POST request with token ==> %#", [result array]);
}failure:nil];
[tokenOperation start];
}
Here is the code for my mapping provider file called MappingProvider:
//MappingProvider.h
#import <Foundation/Foundation.h>
#import <RestKit/RestKit.h>
#interface MappingProvider : NSObject
+(RKMapping *)tokenMapping;
#end
//MappingProvider.m
#import "MappingProvider.h"
#import <RestKit/RestKit.h>
#import "Login.h"
#implementation MappingProvider
+(RKMapping *)tokenMapping
{
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[Login class]];
[tokenMapping addAttributeMappingsFromArray:#[#"token"]];
return tokenMapping;
}
#end
Here is the code for the Login file where 'token' is initialized:
//Login.h
#import <Foundation/Foundation.h>
#interface Login : NSObject
#property (nonatomic, copy)NSString *token;
#end
I'm very new to RESTKit and somewhat new with Objective-C (I've been coding in it for about a month). I would appreciate all help in fixing up my code.
This question is old, but the issue is that requests should use mapping without class specification, because requests use descriptors that specify class (see 2 lines below), like this:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
Probably you also have issue in line:
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:self rootKeyPath:nil method:nil];
more concretely in objectClass:self, it should be objectClass:[self class] or the needed class that contains token property, like this:
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[self class] rootKeyPath:nil method:nil];
Hope this helps

RestKit 0.20.0-rc1 - How do I map multiple error parameters?

I'm currently using the RKErrorMessage class to map an error message from my server like so:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"message" toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
pathPattern:nil
keyPath:#"error"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
But now I would like to map an additional parameter from my server, for instance the error title (or anything else for that matter). My JSON looks like this:
{
"error":{
"title":"This is the error title"
"message":"This is the error message",
}
}
The idea is that I can directly map some of these error messages to UIAlertViews if something ever goes wrong when my app is accessing the server. This can be done already with only the error message but adding a title seems to make it fit well with UIAlertView.
From looking at the RKErrorMessage class I see that it only has two parameters: errorMessage and userInfo. How would I go about doing this?
UPDATE: The reason I'm trying to do this with RKErrorMessage is that it automatically maps to the NSError passed into the failure block in the RestKit get/post/put/etc methods... which is pretty nice.
[objectManager getObjectsAtPath:#"/api/somethingsomething/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
Here's a way to do what you originally asked (not sure why you accepted the answer that didn't actually answer your question):
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"userInfo"]];
RKResponseDescriptor *errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
pathPattern:nil
keyPath:#"error"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
The key difference from your sample code is this line:
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"userInfo"]];
Then, from the failure block callback, you can access the userInfo dictionary like:
NSString *message = [[[[[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey] objectAtIndex:0] userInfo] objectForKey:#"message"];
NSString *title = [[[[[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey] objectAtIndex:0] userInfo] objectForKey:#"title"];
RestKit will automatically map all attributes of the JSON's "error" dictionary to the RKErrorMessage's userInfo dictionary.
Why are you using RKErrorMessage ???
The RKErrorMessage is a simple class used for representing error
messages returned by a remote backend system with which the client
application is communicating.
I think the best way is to create the custom object to map your paramteres.
#interface MyCustomErrorObject : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *message;
#end

Can't handle successful 204 response and correctly map failing response using RestKit

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

Resources