I have a request gives me two part of data, the json looks like this
{
"banner_content":
[
{
"activi_id":"1",
"activi_pic":
},
{
"activi_id":"2",
"activi_pic":
},
],
"categories":
[
{...},
{...},
]
}
When I write responseDescriptor to map the data, I have found I must define a model which contains banner_content array and categories array (I don't want to )
or else I have to write two responseDescriptors to do, when the request is done, I have to get the two parts of data from (RKMappingResult *)mappingResult
then get array like [mappingResult objectForKey:#"banner_content"] and [mappingResult objectForKey:#"categories"]
it's weird
below is my code
File:RCategory.m
#implementation RCategory
+ (NSDictionary *)_mapping {
return #{#"title" : #"title"};
}
+ (RKObjectMapping *)mapping {
// Setup our object mappings
RKObjectMapping *categoryMapping = [RKObjectMapping mappingForClass:[self class]];
[categoryMapping addAttributeMappingsFromDictionary:[[self class] _mapping]];
RKObjectMapping *itemMapping = [RCategoryItem mapping];
RKRelationshipMapping* relationShipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:#"content"
toKeyPath:#"items"
withMapping:itemMapping];
[categoryMapping addPropertyMapping:relationShipMapping];
return categoryMapping;
}
#end
File:RAd.m ignored
File:viewController.m
- (void)loadCategory {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKObjectMapping *categoryMapping = [RCategory mapping];
RKResponseDescriptor *categoryResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"category"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKObjectMapping *adsMapping = [RAd mapping];
RKResponseDescriptor *adResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:adsMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"banner_content"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
//** DO I MUST ADD TWO RESPONSE_DESCRIPTOR FOR ONE REQUEST??**
[objectManager addResponseDescriptor:categoryResponseDescriptor];
[objectManager addResponseDescriptor:adResponseDescriptor];
[objectManager getObjectsAtPath:RPATH(CATEGORY_PATH)
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray* statuses = [mappingResult array];
[self.categories addObjectsFromArray:statuses];
if ([self isViewLoaded]) {
[self.tableView reloadData];
}
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
Does anybody have a better way to solve my problem?
BTW I write -mapping in the Model Class, It's the easiest and best way I can think out
I would like to know how do you write the mapping.
You need 2 response descriptors because the response has 2 logically separate parts. This is fine. If you were using Core Data then you wouldn't be concerned as all your objects would simply be saved to the context and you can fetch them as required. With object mapping, if you don't care about the grouping then you can get an array of all objects from the mapping result.
Creating your mappings from data returned by the model objects is fine - but it does limit you because you can only have one source key per destination key. What happens when you have a different response for the same object with a different key that means the same as some other key (hopefully you don't, but it all depends on the server API).
Related
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:.
I have a special need to send a pre-formatted JSON string to the server. Due to the server using older Microsoft technology the JSON elements MUST be in a certain order. If I use the standard JSON processing of RestKit the JSON elements come from a dictionary and are added in hash order. Sadly this will not work for this one special case.
How can I send a pre-formatted JSON string instead of an NSDictionary that is converted to a JSON string with RestKit 0.2x?
Here is the code for the request using NSDictionary
RKObjectManager *objectManager = self.createObjectManager;
RKObjectMapping *requestMapping = [EssenceRequest.objectMapping inverseMapping];
[objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:EssenceRequest.class
rootKeyPath:nil
method:RKRequestMethodPOST]];
RKObjectMapping *responseMapping = EssenceRoot.objectMapping;
RKResponseDescriptor* essenceResponse = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodPOST
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:essenceResponse];
EssenceRequest *dataObject = [[EssenceRequest alloc] initWithContextAndHandle:uniqueHandle essenceHandle:essenceHandle];
[objectManager postObject:dataObject
path:[NSString stringWithFormat:#"%#%#%#GetEssences", Connection.apiPrefix, Connection.svcMedia, Connection.jsonSecure]
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[serverResponseDelegate serverResponseSuccess:operation mappingResult:mappingResult ];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
[serverResponseDelegate serverResponseFailure:operation error:error];
}];
The EssenceRequest
- (id)initWithContextAndHandle:(NSString *)uniqueHandle essenceHandle:(NSString *)essenceUH;
{
self = [super init];
if (self != nil) {
_request = #{
#"__type" : #"SpecificEssenceLocationRequest:#Messaging.Media",
#"Action" : #"1",
#"ContextUH" : uniqueHandle,
#"EssenceUH" : essenceUH
};
}
return self;
}
+ (RKObjectMapping*)objectMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:EssenceRequest.class];
[mapping addAttributeMappingsFromDictionary:#{
#"request": #"request"
}];
return mapping;
}
The "__type" item must be the first time in the JSON request body. Right now with it being in a dictionary it shows up later in the body when the dictionary is converted to a JSON string.
I know this is poor JSON handling on the server. They will fix it at some point and not require the __type any more but for now I need to send it as needed. I was able to do this in my Android code so I know the request will work once I have the NSString formatted.
Disclaimer: following answer is just my own opinion / suggestion.
Use +[RKMimeTypeSerialization unregisterClass:[RKNSJSONSerialization class]] to unregister default RestKit json serialization class. Then write your own class with "hacked" keys order. Register it through +[RKMimeTypeSerialization registerClass:[RKMYJSONSerialization class] forMINEType:RKMIMETypeJSON]
This way your won't change any API's - just "inject" your code into serialization/deserialization mechanism (and this is what you actually need).
The default implementation of RKNSJSONSerialization is quite simple:
+ (id)objectFromData:(NSData *)data error:(NSError **)error
{
return [NSJSONSerialization JSONObjectWithData:data options:0 error:error];
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error
{
return [NSJSONSerialization dataWithJSONObject:object options:0 error:error];
}
I think, you can go further yourself and write your own, based, of course on NSJSONSerialization or some another JSON serialization mechanism.
Taking Petro's answer a step further. This solution will maintain the functionality of all other requests.
After implementation you can wrap any JSON string in a SPRawJSON to send it as raw JSON for any request.
SPJSONSerialization.h
#import <RestKit/RestKit.h>
#interface SPRawJSON : NSObject
#property (nonatomic, readonly) NSString *json;
-(instancetype)initWithJSON:(NSString*)json;
+(RKObjectMapping*)mapping;
#end
#interface SPJSONSerialization : NSObject <RKSerialization>
#end
SPJSONSerialization.m
#import "SPJSONSerialization.h"
#implementation SPRawJSON
-(instancetype)initWithJSON:(NSString*)json
{
self = [super init];
if (self) {
_json = json;
}
return self;
}
+(RKObjectMapping*)mapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[SPRawJSON class]];
[mapping addAttributeMappingsFromDictionary:#{ #"rawJSON": #"self" }];
return mapping;
}
#end
#implementation SPJSONSerialization
+ (id)objectFromData:(NSData *)data error:(NSError **)error {
return [RKNSJSONSerialization objectFromData:data error:error];
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error {
if ([object isKindOfClass:NSDictionary.class]) {
NSDictionary *dict = object;
id rawJSONObj = dict[#"rawJSON"];
if (rawJSONObj && [rawJSONObj isKindOfClass:SPRawJSON.class]) {
return [[(SPRawJSON*)rawJSONObj json] dataUsingEncoding:NSUTF8StringEncoding];
}
}
return [RKNSJSONSerialization dataFromObject:object error:error];
}
#end
Registering the mapping
RKObjectManager *objectManager = [RKObjectManager sharedManager];
// Make requests bodies be sent as JSON
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
// Add inverse mapping for the request
RKRequestDescriptor *descriptor = [RKRequestDescriptor requestDescriptorWithMapping:[SPRawJSON mapping].inverseMapping objectClass:[SPRawJSON class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:descriptor];
Registering the JSON Serializer
// Replace standard JSON Serializer with our custom one that accepts raw json strings as well (SPRawJSON)
let currentJSONSerializer = RKMIMETypeSerialization.serializationClass(forMIMEType: RKMIMETypeJSON)
RKMIMETypeSerialization.unregisterClass(currentJSONSerializer)
RKMIMETypeSerialization.registerClass(SPJSONSerialization.self, forMIMEType: RKMIMETypeJSON)
Example code for sending request
NSString *myJSON = #"{\"exampleKey\": \"Example value\"}";
SPRawJSON *rawJSON = [[SPRawJSON alloc] initWithJSON:myJSON];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager postObject:rawJSON path:#"foo/bar" parameters:nil success: ... failure: ...]
Notice that the mapping only maps POST requests, so if you want it to work for PUT, etc, you need to map that as well.
Hello friends in stackoverflow:
I am having problem mapping data from the api using restkit.I really want to know where my mistake is.
Json format:
{
data: {
-current_condition: [1]
0: {
cloudcover: "16"
humidity: "59"
- weatherDesc: [1]
0: {
value: "Clear"
}
- weather: [5]
0: {
tempMinC: "10"
tempMinF: "50"
weatherCode: "119"
- weatherDesc: [1]
0: {
value: "Cloudy"
}
.......
}
here is my code how to do the mapping (I tried to mapping the 'cloudcover,humidity',and both 'weatherDesc' in current_condition and weather)
-(void)configureRestKit{
NSURL *baseURL = [NSURL URLWithString:#"http://www.raywenderlich.com"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *currentMapping = [RKObjectMapping mappingForClass:[CurrentCondition class]];
[currentMapping addAttributeMappingsFromArray:#[#"cloudcover",#"humidity",#"weatherDesc"]];
[currentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"current_condition" toKeyPath:#"current_condition" withMapping:currentMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:currentMapping method:RKRequestMethodGET pathPattern:#"/demos/weather_sample/weather.php" keyPath:#"data.current_condition" statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
//weahter Desc
RKObjectMapping *weatherMapping = [RKObjectMapping mappingForClass:[Weather class]];
[weatherMapping addAttributeMappingsFromDictionary:#{#"weatherDesc": #"myweatherDesc"}];
[weatherMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"weather" toKeyPath:#"weather" withMapping:weatherMapping]];
RKResponseDescriptor *weatherresponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:weatherMapping method:RKRequestMethodGET pathPattern:#"/demos/weather_sample/weather.php" keyPath:#"data.weather" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:weatherresponseDescriptor];
}
-(void)loadCurrentCondition{
NSDictionary *queryParams = #{#"format": #"json"};
[[RKObjectManager sharedManager] getObjectsAtPath:#"/demos/weather_sample/weather.php" parameters:queryParams success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_myArr = mappingResult.array;
[self Humidity];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"The error is :%#",error);
}];
}
-(void)Humidity{
restkitCurrentCondition *rkCC = [_myArr objectAtIndex:0];
NSLog(#"///////////////////////the humidity is: %ld",rkCC.humidity.longValue);
NSLog(#"//////////////////// the cloudcover is: %ld",rkCC.cloudcover.longValue);
NSLog(#"/////////////// the weatherDesc is %#",rkCC.weatherDesc[0][#"value"]);
NSLog(#"///////// the weatherDesc in weather is %#",rkCC.restkitweather.myweatherDesc[0][#"value"]);
NSLog(#"///////// the weatherDesc in weather is %#",rkCC.restkitweather.myweatherDesc);
}
here is what I get:
2014-07-16 14:21:36.076 myRestSample[3783:60b] I restkit:RKLog.m:33 RestKit logging initialized...
2014-07-16 14:21:36.154 myRestSample[3783:60b] I
restkit.network:RKObjectRequestOperation.m:150 GET
'http://www.raywenderlich.com/demos/weather_sample/weather.php?format=json'
2014-07-16 14:21:36.289 myRestSample[3783:3a0f] I
restkit.network:RKObjectRequestOperation.m:220 GET
'http://www.raywenderlich.com/demos/weather_sample/weather.php?format=json' (200 OK / 6
objects) [request=0.1323s mapping=0.0024s total=0.1525s]
2014-07-16 14:21:36.289 myRestSample[3783:60b] -[Weather humidity]: unrecognized
selector sent to instance 0x8f8eb30
I tried to change the descritor's keypath to 'nil',looks like this
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:currentMapping
method:RKRequestMethodGET
pathPattern:#"/demos/weather_sample/weather.php" keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
The Weather and CurrentCondition are NSObject and there are four properties in CurrentCondition(cloudcover,humidity, NSArray *weaterDesc,Weather *restkitweather).There are only two properties in Weather.h (NSArray *myweatherDesc,*weatherDesc)
It seems I have already got 6 objects but why I got '[Weather humidity] Unreconized'.
Can anyone help??please..
'[Weather humidity] Unreconized'
This means that you have a Weather Instance, but you are treating it as a current conditions instance.
As per my answer to your other question you should not be using _myArr = mappingResult.array; because you can't be sure what the array contains. Instead you should be using the dictionary and extracting the correct type of object for your needs based on the key path.
I have two EndPoints:
http://rest.domain.com/invite
http://rest.domain.com/template
Depending on what options the User selects, I need to able to PUT, POST, & Delete on both EndPoints.
Mapping for all three methods is slightly different. Mapping for each EndPoint is also different.
What's the best way to setup mappings so they are loaded once and can be used multiple times for each EndPoint depending upon what option (PUT, POST, or Delete) the User selects? I have to accomplish this on one storyboard Scene!
Currently, below is the code that I use when POSTing to the /invite EndPoint (it crashes after the first POST b/c I'm remapping):
- (void)sendInvite:(NSInteger)methodType
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.objectManager = [self getObjectManager];
self.objectManager.managedObjectStore = appDelegate.managedObjectStore;
RKEntityMapping *invitationMapping = [RKEntityMapping mappingForEntityForName:kInvite
inManagedObjectStore:self.objectManager.managedObjectStore];
RKEntityMapping *activityMapping = [RKEntityMapping mappingForEntityForName:kActivity
inManagedObjectStore:self.objectManager.managedObjectStore];
Invite *invitation;
switch (methodType) {
case POST:
{
invitationMapping = [RESTMappingProvider invitionPostMapping:invitationMapping];
activityMapping = [RESTMappingProvider activityPostMapping:activityMapping];
[invitationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:kActivitiesRelationship
toKeyPath:kActivitiesRelationship
withMapping:activityMapping]];
invitation = [self inviteForMethod:POST]; //This method just assigns values to the attributes
[self setupDescriptors:invitationMapping forKeyPath:kMeetupKeyPath descriptorClassIsTemplate:NO];
[self.objectManager.HTTPClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[self.objectManager.managedObjectStore.mainQueueManagedObjectContext saveToPersistentStore:NO];
[self.objectManager postObject:invitation path:kMeetupKeyPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self removeDuplicateObjects]; //After removing relationship Dups, I save to persistent store
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
}
break;
case PUTACCEPT:
case PUTDECLINE:
case PUTEDIT:
{
invitationMapping = [RESTMappingProvider invitionPutMapping:invitationMapping];
activityMapping = [RESTMappingProvider activityPutMapping:activityMapping];
[invitationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:kActivitiesRelationship
toKeyPath:kActivitiesRelationship
withMapping:activityMapping]];
invitation = [self inviteForMethod:methodType]; //This method just assigns values to the attributes
[self setupDescriptors:invitationMapping forKeyPath:kMeetupKeyPath descriptorClassIsTemplate:NO];
[self.objectManager.HTTPClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[self.objectManager putObject:invitation path:kMeetupKeyPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure in PUT");
}];
}
break;
}
}
I do something similar for the Templates EndPoint
- (void)saveAsTemplate
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.objectManager = [self getObjectManager];
self.objectManager.managedObjectStore = appDelegate.managedObjectStore;
RKEntityMapping *invitationMapping = [RKEntityMapping mappingForEntityForName:kInviteTemplates
inManagedObjectStore:self.objectManager.managedObjectStore];
invitationMapping = [RESTMappingProvider invitionTemplateMapping:invitationMapping];
RKEntityMapping *activityMapping = [RKEntityMapping mappingForEntityForName:kActivityTemplates
inManagedObjectStore:self.objectManager.managedObjectStore];
activityMapping = [RESTMappingProvider activityTemplateMapping:activityMapping];
[invitationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:kTemplateActivitiesRelationship
toKeyPath:kTemplateActivitiesRelationship
withMapping:activityMapping]];
STInvitesTemplate *invitation = [self templateForInvite];//this method assigns values to the attributes
[self setupDescriptors:invitationMapping forKeyPath:kTemplatesKeyPath descriptorClassIsTemplate:YES];
[self.objectManager.HTTPClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[self.objectManager postObject:invitation path:kTemplatesKeyPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
}
Request & Response Descriptor:
- (void)setupDescriptors:(RKEntityMapping *)invitationMapping forKeyPath:(NSString *)keyPath descriptorClassIsTemplate:(BOOL)isTemplate
{
RKRequestDescriptor *requestDescriptor;
if (isTemplate)
{
requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[invitationMapping inverseMapping] objectClass:[Template class] rootKeyPath:nil method:RKRequestMethodAny];
}
else
{
requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[invitationMapping inverseMapping] objectClass:[Invite class] rootKeyPath:nil method:RKRequestMethodAny];
}
NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:invitationMapping
method:RKRequestMethodGET
pathPattern:keyPath
keyPath:nil
statusCodes:statusCodeSet];
self.objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[self.objectManager addRequestDescriptor:requestDescriptor];
[self.objectManager addResponseDescriptor:responseDescriptor];
}
I know the approach above is incorrect as Xcode crashes after I POST or PUT. I haven't implemented Delete yet b/c I'm not sure how to set this up correctly.
Do I load the mappings ONCE in viewDidLoad? Do I create PUT, POST, DELETE x 2 EndPoints = 6 RKEntityMappings?
Need some guidance on best practice. Code sample or some step-by-step instructions would be great!
You need to create an many different mappings as you have different structure responses to process. If the responses for one entity type are all subsets of some common superset then you can use just one (for mapping responses and another for requests). You don't say anything about the expected JSON so I can't tell.
In your code I see 2 request descriptors and 1 response descriptor. The requests match against any method so will always be used. The response descriptor matches only GET responses so won't work for anything. You should likely have one response descriptor per end point per method (as you say they need different mappings to be applied to each).
viewDidLoad isn't necessarily the correct place to configure this. It looks like you have a single object manager and this configuration code should run when it is created (not when it is used, brcause the view can be loaded multiple times and the configuration would be duplicated).
I'm stuck with a pagination issue.
When I go to /?PageSize=:perPage&Page=:page" I will get a json response like this:
Which I would like to map using the following paginator:
/* BBActivityPaginator */
RKObjectMapping *activityPaginationMapping = [RKObjectMapping mappingForClass:[BBActivityPaginator class]];
[activityPaginationMapping addAttributeMappingsFromDictionary:#{
#"Page" :#"currentPage",
#"PageSize" :#"perPage",
#"TotalResultCount" :#"objectCount"
}];
[activityPaginationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"PagedListItems" toKeyPath:#"activities" withMapping:activityMapping]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:activityPaginationMapping
pathPattern:#"/?PageSize=:perPage&Page=:page"
keyPath:#"Model.Activities"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
which is related to this mapping:
/* BBActivity */
RKObjectMapping *activityMapping = [RKObjectMapping mappingForClass:[BBActivity class]];
[activityMapping addAttributeMappingsFromDictionary:#{
#"Id" :#"identifier",
#"CreatedDateTime" :#"createdOn",
#"Description" :#"description",
#"CreatedDateTimeOrder" :#"order",
#"Type" :#"type",
#"DeletedActivityItem.Message" :#"deleted"
}];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"User" toKeyPath:#"user" withMapping:userMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"ObservationAdded.Observation" toKeyPath:#"observation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"SightingNoteAdded.SightingNote" toKeyPath:#"observationNote" withMapping:observationNoteMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"SightingNoteAdded.Sighting" toKeyPath:#"observationNoteObservation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"IdentificationAdded.Sighting" toKeyPath:#"identificationObservation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"IdentificationAdded.Identification" toKeyPath:#"identification" withMapping:identificationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"PostAdded.Post" toKeyPath:#"post" withMapping:postMapping]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:activityMapping
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
... For brevity I won't add all the mappings...
When I want to make my call to the paginator, I'm wiring up this:
-(void)setPaginatorForStream:(NSString*)streamName {
[BBLog Log:#"BBStreamController.setPaginatorForStream:"];
[BBLog Debug:#"streamName:" withMessage:streamName];
__weak typeof(self) weakSelf = self;
NSString *streamUrl = [NSString stringWithFormat:#"http://api.blahblah.org.au/%#?PageSize=:perPage&Page=:currentPage&X-Requested-With=XMLHttpRequest", streamName];
if (!self.paginator) {
paginationMapping = [RKObjectMapping mappingForClass:[BBActivityPaginator class]];
RKResponseDescriptor *activitiesResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[RKObjectMapping mappingForClass:[BBActivityPaginator class]]
pathPattern:nil
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
self.paginator = [[BBActivityPaginator alloc]initWithRequest:[NSURLRequest requestWithURL:[[NSURL alloc]initWithString:streamUrl]]
paginationMapping:paginationMapping
responseDescriptors:[[NSArray alloc]initWithObjects:activitiesResponseDescriptor, nil]
andDelegate:weakSelf];
}
self.paginator.perPage = 20;
[self.paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
[weakSelf.tableItems addObjectsFromArray:objects];
[weakSelf.tableView reloadData];
} failure:^(RKPaginator *paginator, NSError *error) {
NSLog(#"Failure: %#", error);
}];
Which is being activated in this method:
-(void)loadRequest {
[BBLog Log:#"BBStreamController.loadRequest"];
self.fetchBatch++;
[self.paginator loadPage:self.fetchBatch];
//[self.paginator setPaginatorLoading:YES];
self.loading = YES;
}
from the class initialisation:
-(BBStreamController*)initWithGroup:(NSString*)groupIdentifier
andDelegate:(id<BBStreamProtocol>)delegate {
[BBLog Log:#"BBStreamController.initWithGroup:andDelegate:"];
self = [self init];
if(self) {
_controller = delegate;
groupId = groupIdentifier;
[self setPaginatorForStream:groupIdentifier];
[self loadRequest];
}
[self loadView];
return self;
}
And getting nothing but pain...
Output:
E restkit:RKPaginator.m:207 Paginator didn't map info to compute page count. Assuming no pages.
2013-05-06 17:48:09.487 BowerBird[26570:5807] W restkit.object_mapping:RKMapperOperation.m:98 Adding mapping error: No mappable values found for any of the attributes or relationship mappings
Any one familiar with the finer details of RestKit 0.2.x's new pagination?
Your pathPattern and keyPath information needs to be set on the activitiesResponseDescriptor definition, not some paginator response descriptor that isn't actually used for anything.
The response descriptor is for the overall response and describes how to:
Tell that we have a match : pathPattern
Find the data to process : keyPath
Create the response objects : data mapping
The paginator mapping is additional to this and is only used to extract the page data from the response being processed.
Move the path pattern and key path to the response descriptor used for the request. The path pattern should also contain "PagedListItems" by the looks of it.