I have a method like this.
- (void)loadData {
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:8080/activiti-rest/service"];
[manager loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectClass:[Data class] delegate:self];
}
I get an error that says:
instance method '-loadObjectsAtResourcePath:objectClass:delegate' not found('return types default to 'id' ').
Can someone please help me how to call loadObjectsAtResourcePath function?
Maybe you want to define the mapping manually. Then just do something like this:
RKObjectMapping *mapping = [[[RKObjectManager sharedManager] mappingProvider] objectMappingForClass:[Data class]];
RKObjectLoader *loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectMapping:mapping delegate:self];
Don't forget to set base resource path to your RKObjectManager singleton.
I had to use the prototype that has objectMapping in it because i was trying to map the response to my own mapping called Data.
So i created an RKObjectMapping object ,configured to my own mapping class and then used this function.
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[Data class]];
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:8080/activiti-rest/service"];
[manager loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectMapping:mapping delegate:self] ;
It worked!
Without knowing RestKit This sounds like a simple header problem. look at the header where "RKObjectManager" is defined. (Cmd click it in XCode to make sure it's available in your project.) Double check the method definition and make sure you're calling it correctly. Always use auto-complete to invoke methods.
Is your object/class implementing the in the interface?
Related
I'm naïvely trying to follow the guidelines for mapping one of my CoreData classes with RestKit 0.20
#import <RestKit/RestKit.h>
...
RKObjectMapping *mymap =
[RKObjectMapping mappingForClass: NSClassFromString(#"MY_CLASS")];
[mymap mapAttributes:#"field1", #"field2", nil];
But
No visible #interface for 'RKObjectMapping' declares the selector 'mapAttribute'
If I type [mymap map, I see only these 2 functions in the completion list
id mappingForDestinationKeyPath:(NSString *)
id mappingForSourceKeyPath:(NSString *)
What am I doing wrong ?
An other example, when I try to use the shared RKObjectManager
[[RKObjectManager sharedManager].mappingProvider setMapping:map forKeyPath:#"/"];
It can't find the member mappingProvider
I think this is a problem with the wiki page, mapAttributes: is not found in the RKObjectMapping docs (http://restkit.org/api/master/Classes/RKObjectMapping.html). I think the wiki page is out of date, use this one instead: https://github.com/RestKit/RestKit/wiki/Object-Mapping
You should use the addAttributeMappingsFromDictionary method instead:
[mymapp addAttributeMappingsFromDictionary:#{#"field1" : #"field1"}];
Also mappingProvider is from pre-0.20 rest kit, and has been replaced with response descriptors (objects which are used to determine which mapping to use for a response)
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:nil keyPath:#"key_path" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:descriptor];
Use mapping like this:
RKManagedObjectMapping * mymap = [RKManagedObjectMapping mappingForClass:[MY_CLASS class] inManagedObjectStore:self.objectStore];
mymap.setDefaultValueForMissingAttributes = YES;
mymap.primaryKeyAttribute = #"MY_CLASS_ID";
mymap.rootKeyPath = #"ROOT_PATH_OF_SERVICE";
[mymap mapKeyPathsToAttributes:
#"WEB_SERVICE_ATTRIBUTE1", #"DATA_MODAL_ATTRIBUTE1",
#"WEB_SERVICE_ATTRIBUTE2", #"DATA_MODAL_ATTRIBUTE2",
#"WEB_SERVICE_ATTRIBUTE3", #"DATA_MODAL_ATTRIBUTE3",
nil];
using loadObjectAtResourcePath on GET method, doesn't include my parameters on the requests.
for example, if I send:
[RKObjectManager objectManagerWithBaseURL:#"http://something/ws"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/res" delegate:self block:^(RKObjectLoader *loader) {
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
#"val", #"param1",
nil];
loader.params = [RKParams paramsWithDictionary:dict];
}];
the final url request doesn't include the "?param1=val" part - why?
Update Months Later
The real answer is that loader.params creates the HTTP BODY, hence it works for POST, PUT, DELETE etc but not for GET where the params are appended to the URL.
Hence, the answer below still works if you're facing the same issue for GET, but if you're sending out GET requests, it's mostly using methods that attach the params to the query string.
To summarize the differences between the two.
Sending params in the HTTP Body(i.e. POST, UPDATE, DELETE)
// Convert a NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:optionValues];
// I use sendObject to skip the router. Otherwise it's normally postObject
[[RKObjectManager sharedManager] sendObject:yourObject toResourcePath: yourResourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.delegate = delegate;
loader.params = params; // This sets params in the POST body and discards your yourObject mapping
} ];
Caveat Emptor (for above)
Setting params in the block destroys any mapping that you might have set in yourObject, kind of defeats the purpose of using object mapping. There's a fix here by Sebastian loader.params - Extra params if you really want to use this method to append extra parameters to your Post not in the object.
Sending in params as Query String (i.e. GET)
// Make a NS dictionary and use stringByAppendingQueryParameters
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" stringByAppendingQueryParameters:shopParams] delegate:objectDelegate];
The rest of the answer is just for reference, I'm a hoarder.
Old Answer
I'm using RestKit for my project and facing the same issue.
I think RKParams is mainly used to do POST requests. I cannot fully decipher your code because 1) I don't know loader's declaration? 2) RKParams is not to be used with Object Manager?
I did this.
Loader Method in App Delegate
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:#"limit",#"30", nil];
[[RKClient sharedClient] get:#"/api/v1/shops.json" queryParams:shopParams delegate:self];
Delegate
- (void)requestDidStartLoad:(RKRequest *)request {
NSLog(#"RK Request description: %#",[request description]);
}
Output:
RK Request description: <RKRequest: 0x7993db0> and rails log say {"limit"=>"30"}.
From the autocomplete in Xcode, you can see the get request didn't even use RKParams. Just a NSDict. The POST requests uses it.
My goal is to attach a query string, i.e. ?location=singapore&etcetc to my API methods in Rails. For this, RK comes with a NSString addon called appendQueryParams RK docs link that you can use to append query params.
If your goal is POST images etc, you can follow the above line of thought of using RKClient.
Update:
If you just want to append parameters to Object Manager
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
This is outdated and marked for deprecation.
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" appendQueryParams:shopParams] delegate:self];
Use this instead:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json stringByAppendingQueryParameters:shopParams] delegate:yourLoaderDelegate];
Rails Log: {"location"=>"latitude,longitude", "limit"=>"20"}
Hope in my answer I didn't make any wrong statements.
Refer to this question RestKit GET query parameters.
I am relatively new to Objective-C and am attempting to use RestKit to receive a JSON response from a web service. I have successfully received the data back to my application, which looks like this viewing the response:
{id:"1","Translation":"Test"}
I would like to map this translation to my "Translation" object in my application, but have tried a few different ways but am not sure how to achieve this.
So my questions are:
How can I map this response to my Translation object
Am I doing this correctly, creating a method to complete this call outwit my view controller?
My Translation Object
#implementation Translation
#synthesize identifier = _identifier;
#synthesize translation = _translation;
- (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"id", #"identifier",
#"translation", #"translation",
nil];
}
#end
My Translate Method
- (NSString *)performTranslation:(NSString *)translation
{
NSString *data = [[NSString alloc] initWithFormat:#"{\"SourceId\": \"%#\",\"RegionTag\": \"%#\",\"InputString\": \"%#\"}", #"1", #"Glasgow", translation];
NSString *post = data;
RKRequest *MyRequest = [[RKRequest alloc] initWithURL:[[NSURL alloc] initWithString:#"http://my.url.com/Translation/Translate"]];
MyRequest.method = RKRequestMethodPOST;
MyRequest.HTTPBodyString = post;
MyRequest.additionalHTTPHeaders = [[NSDictionary alloc] initWithObjectsAndKeys:#"application/json", #"Content-Type", #"application/json", #"Accept", nil];
[MyRequest send];
RKResponse *Response = [MyRequest sendSynchronously];
return Response.bodyAsString; <--- looking to map this to translation object here
}
The snippet of your code seems a bit outdated. I strongly recommend reading the newest Object Mapping guide in order to leverage RestKit into it's fullest potential - especially the part Mapping without KVC.
Edit:
In order to post an object with RestKit and receive back an answer, we define a TranslationRequest class that will hold our request & Translation to hold our response.
Firstly, we set up our RKObjectManager and mappings (i usually do this in my AppDelegate):
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:kOurBaseUrl];
[manager setSerializationMIMEType:RKMIMETypeJSON];
//this is a singleton, but we keep the manager variable to avoid using [RKObjectManager sharedManager] all the time
//Here we define a mapping for the request. Note: We define it as a mapping from JSON to entity and use inverseMapping selector later.
RKObjectMapping *translationRequestMapping = [RKObjectMapping mappingForClass:[TranslationRequest class]];
[translationRequestMapping mapKeyPath:#"RegionTag" toAttribute:#"regionTag"];
...
[[manager mappingProvider] setSerializationMapping:[translationRequestMapping inverseMapping] forClass:[TranslationRequest class]];
//now we define the mapping for our response object
RKObjectMapping *translationMapping = [RKObjectMapping mappingForClass:[Translation class]];
[translationMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[translationMapping mapKeyPath:#"Translation" toAttribute:#"translation"];
[[manager mappingProvider] addObjectMapping:mapping];
//finally, we route our TranslationRequest class to a given endpoint
[[manager router] routeClass:[TranslationRequest class] toResourcePath:kMyPostEndpoint];
This should be enough of the necessary setup. We can call our backend anywhere in the code (e.g. in any controller) like this:
//we create new TranslationRequest
TranslationRequest *request = [[TranslationRequest alloc] init];
[request setRegionTag:#"Hello"];
....
//then we fetch the desired mapping to map our response with
RKObjectMapping *responseMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:class]
//and just call it. Be sure to let 'self' implement the required RKObjectManagerDelegate protocol
[[RKObjectManager sharedManager] postObject:request mapResponseWith:responseMapping delegate:self];]
Try this approach and let me know if you need any assistance.. I was not able to test it fully as i don't have any suitable backend that will return the responses, but judging from the RestKit log this should work.
You need to pass the returned JSON string into a JSON parser. I use SBJSON. You can then use the resulting dictionary to populate the properties of your object.
RestKit seems to have native objects that encapsulate four different JSON parsers. However, I'd advise caution because they seem to assume that the top level parsed object will always be a dictionary.
As another aside, the example in your question is not valid JSON. It should look like this:
{"id":"1","Translation":"Test"}
I'm having trouble mapping JSON to my entities.
Error message is:
Adding mapping error: Could not find an object mapping for keyPath:
''.
I am using the following code:
RKObjectMapping *lEgmMapping = [RKObjectMapping mappingForClass:[EGM
class]];
[lEgmMapping mapKeyPath:#"Id" toAttribute:#"Id"];
[lEgmMapping mapKeyPath:#"InternalId" toAttribute:#"internalId"];
[lEgmMapping mapKeyPath:#"PlayerType" toAttribute:#"playerType"];
[lEgmMapping mapKeyPath:#"PositionX" toAttribute:#"x"];
[lEgmMapping mapKeyPath:#"PositionY" toAttribute:#"y"];
[lEgmMapping mapKeyPath:#"Size" toAttribute:#"size"];
[lEgmMapping mapKeyPath:#"Status" toAttribute:#"status"];
[[RKObjectManager sharedManager].mappingProvider
setMapping:lEgmMapping forKeyPath:#"GetEgmMarkersBySiteResult"];
And I'm obtaining the following JSON from the web service:
{"GetEgmMarkersBySiteResult":[{"Id":
94,"InternalId":"A2345","PlayerType":"2","PositionX":686,"PositionY":
1460,"Size":23,"Status":"0"},{"Id":
83,"InternalId":"A1002a","PlayerType":"0","PositionX":1414,"PositionY":
906,"Size":30,"Status":"0"},{"Id":
81,"InternalId":"asd","PlayerType":"0","PositionX":1338,"PositionY":
925,"Size":33,"Status":"0"}]}
What am I doing wrong?
Thank you.
EDIT:
Turns out what happens is that it could not find the mapping when it was on the appdelegate applicationlaunchedwithoptions, which I thought was a good place to load bindings. Where do you think is a good place to load bindings? could it cause a runtime error if another viewcontroller defines the same binding?
I am testing out RestKit and need to access different BaseUrls and also sometimes access a web service with the same baseUrl from different places "at once", lastly I also need to access the same baseUrl with different ressourcePaths in the same controller.
In my app delegate I set up the RKObjectManager singleton like this.
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:kBaseUrl];
[objectManager registerClass:[EntityClass1 class] forElementNamed:#"element1"];
[objectManager registerClass:[EntityClass2 class] forElementNamed:#"element2"];
.
.
.
etc.
The singleton approach is really easy to work with, I however can't figure out how to separate the different web service calls.
In MyViewController, which implement the RKObjectLoaderDelegate, I will have the two methods:
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
//stuff with result
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
//stuff with error
}
This causes no problems when MyViewController uses one RKObjectManager singleton to access one ressourcePath with one baseUrl.
If I start different requests in this way:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:FLICKRPath delegate:self]
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:FOURSQUAREPath delegate:self]
and so on, within the same MyController, my problem is that FLICKRPath and FOURSQUAREPath of course has different baseUrl, but the RKObjectManager only has one?
If I get this working and can have different RKObjectManagers another problem arises.
The delegate methods didLoadObjects and didFailWithError will receive results from both RKObjectManagers and I can't see any other way to tell them apart than from their baseUrls. Potentially comparing each return value with a baseUrl and, even worse, a ressourcePath, in the delegate method does not appeal to me at all.
If I have different RKObjectManagers I guess I could pass them different delegates and build classes dedicated to deal with the return values from different baseUrls and ressourcePaths. This would mean I had to build yet another abstraction on top of MyController and RestKit, which also seems messy.
I have a strong feeling I am going about this in the wrong way, the RestKit source is very impressive which indicates that is me fighting the framework. I would really appreciate some best practice insights on the subject. I have been through all the resources and examples that I could find but have not seen the above use case. It is always one RKObjectManager, one baseUrl and one ressourcePath.
Thank you in advance.
Since there is no accepted answer yet: using multiple object managers is quite simple using RestKit.
From the Wiki (Using Multiple Base URLs (and Multiple Object Managers):
The first object manager you create will be the shared singleton
RestKit uses by default. But by creating additional object managers,
you can pull from their BaseURLs as needed, just be sure to retain
these new managers.
RKObjectManager *flickrManager =
[RKObjectManager objectManagerWithBaseURL:flickrBaseUrl]; // <-- shared singleton
RKObjectManager *foursquareManager =
[[RKObjectManager objectManagerWithBaseURL:foursquareBaseUrl] retain]; // <-- you must retain every other instance.
Depending on your application, you may want to put this second object
manager in a more accessible place, like a retained property on the
AppDelegate, so that it's easy to pull from as needed.
In the event that you need to differentiate between the results from
your two (or more) object managers, simply set an identifier in the
userData for the queries.
- (void)someAction(id)sender {
// .......
RKObjectLoader* loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/whatever" delegate:self];
loader.userData = #"foursquare";
// or do this, if you need a number instead of a string
loader.userData = [NSNumber numberWithInt:1234];
// .......
}
//Then when the delegate comes back you can cast it into a string or number as appropriate:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
// .......
NSString* source = (NSString*) objectLoader.userData;
// or, if you did the NSNumber instead:
NSNumber* source = (NSNumber*) objectLoader.userData;
// .......
}
API change:
RKObjectLoader* loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/whatever" delegate:self];
doesn't compile in RestKit v.0.10.3 (loadObjectsAtResourcePath:delegate: returns void). That method just wraps a few lines of code, though, so you can still get at the loader, and add userData, with the following:
RKObjectLoader *loader = [[RKObjectManager sharedManager] loaderWithResourcePath:resourcePath];
loader.userData = #"SOMEDATA";
loader.delegate = self;
loader.method = RKRequestMethodGET;
[loader send];
(adding note in case other new users run into the same issues I did).
And by the way, since userData property is also available on RKRequest, you can use the same approach for loading/identifying requests.
For example, some post request:
RKClient * client = [RKClient sharedClient];
[client post:#"/your-api-path" usingBlock:^(RKRequest *request) {
request.userData = #"<some-object-you-can-check-in-delegate-callback>";
request.params = someParamsForRequest;
request.delegate = <delegate you want to call when request is finished>;
}];
How about using objectLoader.
You'll find the mapped object type/Class objectLoader.objectMapping.objectClass and add your conditions based on it instead of the url
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// your condition based on -> objectLoader.objectMapping.objectClass
}
Hope it will help
Possible approach is to introduce one singletone for each base url.
You can instantiate as many RKObjectManager objects as you want. However, only the first one will become shared. Look into initWithHTTPClient: sources.
if (nil == sharedManager) {
[RKObjectManager setSharedManager:self];
}
We can't use default sharedManager method to target specific object manager but we can easily implement our own singleton. Here's an example for Google Maps object manager:
#implementation GMObjectManager
+ (GMObjectManager*)sharedManager
{
static GMObjectManager *manager; // keep reference
if (!manager) {
// init with custom base url
NSURL *baseUrl = [NSURL URLWithString:kGMBaseUrl];
manager = [GMObjectManager managerWithBaseURL:baseUrl];
}
return manager;
}
- (id)initWithHTTPClient:(AFHTTPClient *)client
{
self = [super initWithHTTPClient:client];
if (self) {
// additional initialization
}
return self;
}
#end
Usage:
CGObjectManager *googleMapsManager = [GMObjectManager sharedInstance];