how to i set response mapping to manager with path pattern ..if the getobjects at path is different from path pattern that is used to map the response.
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:A
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
[manager getObjectsAtPath:A/ID
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#" Category success");
[self.delegate didReceiveAssignedCategories];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Category failure");
}];
response mapping path ie:A must be set to dynamic path used to getobject ie:A/ID .
ex:
Call 1)
A = /getAllCategories
A/ID = /getAllCategories/123
call 2)
A = /getAllCategories
A/ID = /getAllCategories/456
response mapping is same for 123, 456
only while getting the objects i am using different urls ie: with id's attached.
how to do that ?
If you have 2 path patters which both return the same type of data then you can use the same mapping with 2 different response descriptors.
If you have 1 path pattern which can return 2 different types of data then you need to use RKDynamicMapping to 'intercept' the incoming data and decide which mapping is actually required.
From your edited question, 'pattern' is the important thing that you have misunderstood. You need to use a path pattern, not a static path:
#"getAllCategories/:identity"
1) First create response mapping like
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:#"getAllCategories/:categoryID"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
2)Create Class with categoryID in it.
[CategoryRequest class]
3) create object of that class and set categoryID
CategoryRequest *categoryRequest = [CategoryRequest alloc] init];
categoryRequest.categoryID = #"123";
4)call getobject using that object
[manager getObject:categoryRequest
path:#"getAllCategories/123"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
if another call is required to be made for same mapping create object of of category request class set new category id and call get object using that categoryresquest and required path patter.
Related
Is there a way to differ between mapping results which are empty due to there being no data do map att the key path and a mapping result which is empty due do skipping objects through KVC.
The problem I have is that I am working with a API which is not very well thought through, and I need to find a way to know when I am at the end of the page. The issue is that there is a possibility that the mapping result can be empty due to all the objects being returned are "deleted".
TLDR; Is there a way to see the amount of mapping errors through mapping result on a completed request.
EDIT
I solved my own problem by reading the documentation again. The solution was to create a subclass of RKObjectRequestOperation and then register it.
[[RKObjectManager sharedManager] registerRequestOperationClass:[CustomRequestOperation class]];
This mean that I could implement the following which would listen to mapping errors and increment a counter and add it to mappingMetaData when the mapping completed.
- (void)setCompletionBlockWithSuccess:(void ( ^ ) ( RKObjectRequestOperation *operation , RKMappingResult *mappingResult ))success failure:(void (^) ( RKObjectRequestOperation *operation , NSError *error ))failure {
__block typeof(skipCount) temp = skipCount;
[super setCompletionBlockWithSuccess:^void(RKObjectRequestOperation *operation , RKMappingResult *mappingResult) {
if (success) {
operation.mappingMetadata = #{#"skipCount" : #(temp)};
success(operation, mappingResult);
}
} failure:^void(RKObjectRequestOperation *operation , NSError *error) {
if (failure) {
failure(operation, error);
}
}];
}
- (void)mapper:(RKMapperOperation *)mapper didFailMappingOperation:(RKMappingOperation *)mappingOperation forKeyPath:(NSString *)keyPath withError:(NSError *)error {
skipCount++;
}
- (void)mapperWillStartMapping:(RKMapperOperation *)mapper {
skipCount = 0;
}
how to i set response mapping to manager with path pattern ..if the getobjects at path is different from path pattern that is used to map the response.
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:A
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
[manager getObjectsAtPath:A/ID
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#" Category success");
[self.delegate didReceiveAssignedCategories];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Category failure");
}];
response mapping path ie:A must be set to dynamic path used to getobject ie:A/ID .
ex:
Call 1)
A = /getAllCategories
A/ID = /getAllCategories/123
call 2)
A = /getAllCategories
A/ID = /getAllCategories/456
response mapping is same for 123, 456
only while getting the objects i am using different urls ie: with id's attached.
how to do that ?
If you have 2 path patters which both return the same type of data then you can use the same mapping with 2 different response descriptors.
If you have 1 path pattern which can return 2 different types of data then you need to use RKDynamicMapping to 'intercept' the incoming data and decide which mapping is actually required.
From your edited question, 'pattern' is the important thing that you have misunderstood. You need to use a path pattern, not a static path:
#"getAllCategories/:identity"
1) First create response mapping like
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:#"getAllCategories/:categoryID"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
2)Create Class with categoryID in it.
[CategoryRequest class]
3) create object of that class and set categoryID
CategoryRequest *categoryRequest = [CategoryRequest alloc] init];
categoryRequest.categoryID = #"123";
4)call getobject using that object
[manager getObject:categoryRequest
path:#"getAllCategories/123"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
if another call is required to be made for same mapping create object of of category request class set new category id and call get object using that categoryresquest and required path patter.
I am posting to my rails server using the post object method.
[manager postObject:recipe path:#"/api/recipes" parameters:#{ #"auth_token": fbAccessToken } success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"mappingResult: %#", mappingResult);
}];
This works as intended, however my Rails app is returning the json of the Recipe. How do I access this value?
All returned values are in mappingResult variable.
RKMappingResult object has many methods that will return the serialized JSON when set-up properly. Such methods are: dictionary and array. You can look at the header file for other methods.
Also be sure to read documentation that can be found on RestKit website and a lot of things will be easier for you.
I am doing a simple GET request with AFNetworking
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Once I have made the request I want to be able to access the responseObject from any other method in the class.
I want to be able to save the responseObject so I can do something like display the output in a tableview.
It's common to creat object models that will be represented by JSON. When you get the response you would then parse the data into the models. The approach we use is to return the response to the requester through a completion block. You don't have to parse the JSON into strongly typed objects, but it really is helpful long term. It's probably a good idea to farm out the network request operations into a separate class (called a service) as well. This way you can instantiate a new service and get notified through a completion block that it is finished. For example your service's request signature could look like this:
typedef void(^HelloWorldCompletionHandler)(NSString *helloWorld, NSError *error);
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler;
// implementation
- (void)requestHelloWorldData:(HelloWorldCompletionHandler)completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
id JSONResponse = [operation responseObject];
if (operation.error) {
completionHandler(nil, error);
} else {
// parse the response to something
id parserResult = [self parseJSONResponse:JSONResponse];
completionHandler(parserResult, nil);
}
}];
This way you'll know when the network request is complete, and you can set the data you want on a property within your class. Then you could call tableView.reloadData in order to use the data in your table.
All that code would go into a service type class. I like to organize my services by responsibility. I don't know how many different data calls you make, but we have several for our project. If for instance you were making a weather app you could potentially organize by Current Conditions, Daily Forecasts, and Hourly Forecasts. I would make a service for each one of these requests. Say I created a CurrentConditionsService. The header would look something like this:
typedef void(^CurrentConditionsCompletionHandler)(CurrentConditions *currentConditions, NSError *error);
#interface CurrentConditionsService : NSObject
// locationKey is some unique identifier for a city
+ (instancetype)serviceWithLocationKey:(NSString *)locationKey;
- (void)retrieveCurrentConditionsWithCompletionHandler:(CurrentConditionsCompletionHandler)completionHandler;
#end
Then in my implementation file I would make the request and invoke the given completion handler like I demonstrated above. This pattern can be followed by many different services to the point where all your services could inherit from a base class that handles the request/response portions. Then your subclasses could override specific methods and handle/parse the data appropriately based on type.
If you go the route of parsing the JSON responses into model objects, all your parsers will need to conform to a protocol. This way in your super class it doesn't matter what the concrete implementation of your parser is. You supply the super class with a concrete implementation and all it knows how to do is invoke the parser and return the response.
An example JSON parser protocol would look like this:
#protocol AWDataParser <NSObject>
#required
- (id)parseFromDictionary:(NSDictionary *)dictionary;
- (NSArray *)parseFromArray:(NSArray *)array;
#end
And invoking it in your services super class:
- (id)parseJSONResponse:(id)JSONResponse error:(NSError **)error {
NSAssert(self.expectedJSONResponseClass != nil, #"parseResponse: expectedJSONResponseClass cannot be nil");
NSAssert(self.parser != nil, #"parseResponse: parser cannot be nil");
id parserResult = nil;
if (![JSONResponse isKindOfClass:self.expectedJSONResponseClass]) {
//handle invalid JSON reponse object
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:#{#"Invalid JSON type": [NSString stringWithFormat:#"expected: %#, is: %#",self.expectedJSONResponseClass, [JSONResponse class]]}];
}
} else {
if (self.expectedJSONResponseClass == [NSArray class]) {
parserResult = [self.parser parseFromArray:JSONResponse];
}else {
parserResult = [self.parser parseFromDictionary:JSONResponse];
}
if (!parserResult) {
if (error) {
*error = [NSError errorWithDomain:NetworkServiceErrorDomain code:kNetworkServiceErrorParsingFailure userInfo:nil];
}
}
}
return parserResult;
}
Use this approach:
NSURL *COMBINAT = [[NSURL alloc] initWithString:#"http://someapi.com/hello.json"];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
COMBINAT];
[self performSelectorOnMainThread:#selector(savedata:) withObject:data waitUntilDone:YES];
});
then simply call:
- (void)savedata:(NSData *)responseData {
NSError* error;
NSLog(#"Answer from server %#", responseData);
// ... your code to use responseData
}
Just create a property:
#property(nonatomic, strong) id savedResponseObject;
and set it in the success handler of the request:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://someapi.com/hello.json"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
self.savedResponseObject = responseObject;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#", error);
}];
Then you will be able to access it from other places in your class by referencing:
self.savedResponseObject
My JSON response looks like this:
{
"enabled": false,
"number_of_articles": 20,
"new_users": [
{
"id": "5001",
"name": "Jimmy Valner"
},
],
"articles" : [
{
"id" : 33122,
"title" : "Something",
"authors": [
{
"id": "511",
"name": "Somebody"
},
{
"id": "51",
"name": "Somebody else"
}
}]
}
Not really a proper RESTful response I know. But I have to map it somehow to two different Entities (User and Article). A already have a working code for the articles part. It maps the Article and the Articles as a relation.
I'm doing it like this:
RKMapping *mapping = [MappingProvider articlesMapping];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:mapping
pathPattern:#"/articles/:userId/latest"
keyPath:#"articles"
statusCodes:statusCodeSet];
NSURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:nil
method:RKRequestMethodGET
path:[NSString stringWithFormat:#"/articles/%#/latest", [[CredentialStore sharedInstance] userId]]
parameters:nil];
RKManagedObjectStore *store = [[DataModel sharedDataModel] objectStore];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request
responseDescriptors:#[responseDescriptor]];
operation.managedObjectCache = store.managedObjectCache;
operation.managedObjectContext = store.mainQueueManagedObjectContext;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"%#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#", error);
NSLog(#"Response: %#", operation.HTTPRequestOperation.responseString);
}];
[operation start];
This is working as expected. I provide the mapping instructions in the + (RKMapping *)articlesMapping method. Even the authors part of the JSON response is mapped properly.
Now I want to map new_users array to a different entity (User obviously). How can I do that by not invoking another request to the server ? I had some success with creating another RKManagedObjectRequestOperation *operation but that of course triggers another network request to the server which I want to avoid.
Should I look for the solution here in the RKObjectManager or should I somehow change the keyPath to start at root level and the do the mapping for different entities in the + (RKMapping *) method (probably not) ?
And another question .. how can I access the enabled, number_of_articles properties from this JSON? Again, I'm not dealing with a nice structured Restful response, but I have to deal with it somehow. I only want to access this properties somehow ... no mapping to entities needed.
RKManagedObjectRequestOperation has the method initWithRequest:responseDescriptors: for a reason. Note specifically the 's' on responseDescriptors and the fact that you're passing an array. This means you can configure multiple response descriptors, each with the same path pattern but different key paths and RestKit will process each of them on the response received from the server.
Note that when you do this you probably want to change your NSLog(#"%#", mappingResult.array); because it would contain all of the top level items in the list. Instead, you should probably use NSLog(#"%#", mappingResult.dictionary);