iam using restKit, to send and receive data from server... iam getting back
{
"request":"globalUpdate",
"updateRevision":2,
"updatedObjects":{
"users":[
{
id:"someid1",
name:"somename"
},
{
id:"someid2",
name:"somename2",
}
]
}
}
i want to use
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:nil usingBlock:^(RKObjectLoader * loader){)];
to load only objects inside updatedObjects into CoreData and request, updateRevision into NSDictionary
so in
loader.onDidLoadObjects = ^(NSArray *objects) {
}
the first object is the Dictionary and the later one are CoreData
Well this is the matter of your choice which object you use for the coredata and which for your own purpose. RKObjectLoader also provides onDidLoadResponse block which has a reference to the response which you could use for your own use.
[myobjectManager loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.mappingProvider = [RKObjectMappingProvider mappingProviderUsingBlock:^(RKObjectMappingProvider *provider) {
[provider setObjectMapping:[MyClass mapping] forKeyPath:#"updatedObjects"];
loader.onDidLoadObjects = ^(NSArray *objects){
};
loader.onDidLoadResponse = ^(RKResponse *response){
// NSData * data = [response data];
id object = [response parsedBody:nil];
// now parse the data yourself which will give you
// the entire json in NSData for and parse it,
// extract the component you need
};
loader.onDidFailWithError = ^(NSError *error){
};
}];
}];
Related
I'm trying to run a find by ID request on a REST API. I'm using RestKit 0.20. I have a Location object. It has an id attribute. I want to make a GET request to '/locations/:id' and receive the complete object as JSON.
I have the backend and it's working. I'm now trying to write the iOS client code.
Here's what I have:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping requestMapping];
[lmap addAttributeMappingsFromArray:#[#"id"]];
RKRequestDescriptor* req = [RKRequestDescriptor requestDescriptorWithMapping:lmap objectClass:[Location class] rootKeyPath:nil];
[m addRequestDescriptor:req];
Location* l = [[Location alloc] init];
l.id = [NSNumber numberWithInt:177];
[m getObject:l path:#"locations/:id" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult firstObject]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
After the code above is ran Restkit does not replace ':id: from the path with the ID attribute set in the Location object.
Do you guys have any ideas what I'm doing wrong?
UPDATE:
I had both request and response descriptors set for the Location class. I had a route added for the find_by_id request but it was a Named Route, not a Class Route. When I used the getObject:path:parameters:success:failure method the router did not fill in the 'id' placeholder (irregardless whether it was named 'id', 'object_id', 'identity' or whatever).
The solution I found is this:
Continue using a Named Route but use the getObjectsAtPathForRouteNamed:object:parameters:success:failure method instead
User a Class Route and continue using the getObject:path:parameters:success:failure method
The problem I was having was that when using a NamedRoute like so:
RKRoute * route = [RKRoute routeWithClass:className pathPattern:path method:RKRequestMethodFromString(method)];
[objectManager.router.routeSet addRoute:route];
and then querying for objects using the getObject:path:parameters:success:failure method did not cause the router to fill out any placeholders in the URL path.
You're using a request descriptor, but your aren't making a 'request' (PUT / POST). When doing a GET you need to use a response descriptor. Also, the mapping you're creating isn't specifying the class (so it's linked against NSDictionary. I'd usually use the response descriptor with a router too. Something like:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping mappingForClass:[Location class]];
[lmap addAttributeMappingsFromArray:#[#"identity"]];
RKResponseDescriptor* req = [RKResponseDescriptor responseDescriptorWithMapping:lmap pathPattern:#"locations/:identity" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
[m addResponseDescriptor:req];
[m.router.routeSet addRoute:[RKRoute routeWithClass:[Location class] pathPattern:#"locations/:identity" method:RKRequestMethodGET]];
Location* l = [[Location alloc] init];
l.identity = [NSNumber numberWithInt:177];
[m getObject:l path:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult array]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
I'm sending off 2 requests simultaneously with Restkit and I receive a response back from both of them, but only one of the requests receives any objects. If I send them off one by one, then my objectloader receives all objects.
First request:
self.firstObjectManager = [RKObjectManager sharedManager];
[self.firstObjectManager loadObjectsAtResourcePath:[NSString stringWithFormat: #"/%#.json", subUrl] usingBlock:^(RKObjectLoader *loader){
[loader.mappingProvider setObjectMapping:designArrayMapping forKeyPath:#""];
loader.userData = #"design";
loader.delegate = self;
[loader sendAsynchronously];
}];
Second request:
self.secondObjectManager = [RKObjectManager sharedManager];
[self.secondObjectManager loadObjectsAtResourcePath:[NSString stringWithFormat: #"/%#.json", subUrl] usingBlock:^(RKObjectLoader *loader){
[loader.mappingProvider setObjectMapping:designerMapping forKeyPath:#""];
loader.userData = #"designer";
loader.delegate = self;
[loader sendAsynchronously];
}];
My objecloader:
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects
{
//NSLog(#"This happened: %#", objectLoader.userData);
if (objectLoader.userData == #"design") {
NSLog(#"Design happened: %i", objects.count);
}else if(objectLoader.userData == #"designer"){
NSLog(#"designer: %#", [objects objectAtIndex:0]);
}
}
My response:
2012-11-18 14:36:19.607 RestKitTest5[14220:c07] Started loading of request: designer
2012-11-18 14:36:22.575 RestKitTest5[14220:c07] I restkit.network:RKRequest.m:680:-[RKRequest updateInternalCacheDate] Updating cache date for request <RKObjectLoader: 0x95c3160> to 2012-11-18 19:36:22 +0000
2012-11-18 14:36:22.576 RestKitTest5[14220:c07] response code: 200
2012-11-18 14:36:22.584 RestKitTest5[14220:c07] Design happened: 0
2012-11-18 14:36:22.603 RestKitTest5[14220:c07] I restkit.network:RKRequest.m:680:-[RKRequest updateInternalCacheDate] Updating cache date for request <RKObjectLoader: 0xa589b50> to 2012-11-18 19:36:22 +0000
2012-11-18 14:36:22.605 RestKitTest5[14220:c07] response code: 200
2012-11-18 14:36:22.606 RestKitTest5[14220:c07] designer: <DesignerData: 0xa269fc0>
Update: Setting my base url
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://iphone.meer.li"];
[RKObjectManager setSharedManager:[RKObjectManager managerWithBaseURL:baseURL]];
Solution
Problem was that I used the shared manager for both object managers, so I ended up doing:
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://iphone.meer.li"];
RKObjectManager *myObjectManager = [[RKObjectManager alloc] initWithBaseURL:baseURL];
self.firstObjectManager = myObjectManager;
and:
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://iphone.meer.li"];
RKObjectManager *myObjectManager = [[RKObjectManager alloc] initWithBaseURL:baseURL];
self.secondObjectManager = myObjectManager;
Use block syntax for loading,
self.secondObjectManager = [RKObjectManager sharedManager];
[self.secondObjectManager loadObjectsAtResourcePath:[NSString stringWithFormat: #"/%#.json", subUrl] usingBlock:^(RKObjectLoader *loader){
[loader.mappingProvider setObjectMapping:designerMapping forKeyPath:#""];
loader.userData = #"designer";
loader.onDidLoadObjects = ^(NSArray *objects){
NSLog(#"Loaded objects %#", objects);
};
loader.onDidFailedWithError = ^(NSError *error){
NSLog(#"Failed with error %#", error.userInfo);
}
}];
Edit
Use your own object manager and retain it and then use it this way,
Do not use sharedManager instead alloc/init it and provide it objectstore and see. Hope this helps you,
[self.objectManager loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader){
[loader.mappingProvider setMapping:[MyClass mapping] forKeyPath:#""];
loader.onDidLoadObjects = ^(NSArray *objects){
};
loader.onDidLoadResponse = ^(RKResponse *response){
NSLog(#"Loaded response %#", response.bodyAsString);
};
loader.onDidFailLoadWithError = ^(NSError *error){
NSLog(#"Error occurred ... %#", error.userInfo);
};
}];
So, here is my question.
What I want to do, is to queue resources loading; so that every resource request load is done one at a time, and one after the other (controlling the order I receive those resources).
What would be the correct, and cleaner way to complete such a behaviour?
Ok, I think I got it.
Every RKObjectLoader is a subclass of a RKRequest object. So it may be associated to a customized RKRequestQueue.
Let that RKRequestQueue be configured so that only 1 element can be treated at a time we could achieve the ordering. By setting its concurrency to 1.
I have the pseudo-code :
Create a RKRequestQueue and define its concurrency to 1.
Mark it as suspended, so that it will wait load until I'm done with queueing my resource loading requests.
Loop over my resource loading requests and add them in the order I want them to be executed.
We start with a lazy loading of the request queue
- (RKRequestQueue *)mainDownloadRequestQueue {
if (!_mainDownloadRequestQueue) {
// Creating the request queue
RKRequestQueue * queue = [RKRequestQueue requestQueue];
//queue.delegate = self;
queue.concurrentRequestsLimit = 1;
queue.showsNetworkActivityIndicatorWhenBusy = YES;
// Start processing!
[queue start];
_mainDownloadRequestQueue = [queue retain];
}
return _mainDownloadRequestQueue;
}
And the main methods could/should look like this, we set the queue in the blocks, just before it is checked by RestKit that a queue is available for handling download.
- (void)setUpMainQueue {
// We lock the download queue so that, no download will start until, we want it
[[self mainDownloadRequestQueue] setSuspended:YES];
// Fill up the queue
[self fillQueueWithMandatoryDownloads];
// No, let's start and wait for data to be there
[[self mainDownloadRequestQueue] setSuspended:NO];
}
- (void)fillQueueWithMandatoryDownloads {
// Add the first request
[self addLanguagesRequest];
// Add another request
[self addLanguagesRequest];
// … Add any other request
}
- (void)addLanguagesRequest {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager.client.baseURL = [RKURL URLWithString:kFoundationHost];
__unsafe_unretained OMResourceLoader * wSelf = self;
[objectManager loadObjectsAtResourcePath:#"/sources/api/languages" usingBlock:^(RKObjectLoader * loader) {
// Set the queue there, this is the one defined
loader.queue = [wSelf mainDownloadRequestQueue];
// Do other configuration or behaviour for that
loader.onDidLoadObjects = ^(NSArray *objects) {
[[OMLogManager sharedLogManager] log:[objects description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
}];
}
- (void)addCategoriesRequest {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager.client.baseURL = [RKURL URLWithString:kFoundationHost];
__unsafe_unretained OMResourceLoader * wSelf = self;
[objectManager loadObjectsAtResourcePath:#"/sources/api/categories" usingBlock:^(RKObjectLoader * loader) {
// Set the queue
loader.queue = [wSelf mainDownloadRequestQueue];
loader.onDidFailLoadWithError = ^(NSError * error) {
[[OMLogManager sharedLogManager] log:#"Categories loading error" logLevelParam:OM_LOG_LEVEL_ERROR exceptionParam:nil errorParam:error];
};
loader.onDidFailWithError = ^(NSError * error) {
[[OMLogManager sharedLogManager] log:#"Categories loading error" logLevelParam:OM_LOG_LEVEL_ERROR exceptionParam:nil errorParam:error];
};
loader.onDidLoadResponse = ^(RKResponse *response) {
[[OMLogManager sharedLogManager] log:[[response bodyAsString] description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
loader.onDidLoadObjects = ^(NSArray *objects) {
[[OMLogManager sharedLogManager] log:[objects description] logLevelParam:OM_LOG_LEVEL_INFO exceptionParam:nil errorParam:nil];
};
}];
}
It won't let me attached the params to the request, what am I doing wrong? Params is a Dictionary and endString adds to the sharedClient baseURL.
[[RKClient sharedClient] get:endString usingBlock:^(RKRequest *loader){
loader.params = [RKParams paramsWithDictionary:params];
loader.onDidLoadResponse = ^(RKResponse *response) {
[self parseJSONDictFromResponse:response];
};
loader.onDidFailLoadWithError = ^(NSError *error) {
NSLog(#"error2:%#",error);
};
}];
I get this error:RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?
I think you are on the right track. Below is from a working example I found here, about 2/3 the way down the page. Another option for you may be to append the params directly to the URL. I'm not sure if that's feasible for you, but if your parameters are simple then it may be.
- (void)authenticateWithLogin:(NSString *)login password:(NSString *)password onLoad:(RKRequestDidLoadResponseBlock)loadBlock onFail:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
[[RKClient sharedClient] post:#"/login" usingBlock:^(RKRequest *request) {
request.params = [NSDictionary dictionaryWithKeysAndObjects:
#"employee[email]", login,
#"employee[password]", password,
nil];
request.onDidLoadResponse = ^(RKResponse *response) {
id parsedResponse = [response parsedBody:NULL];
NSString *token = [parsedResponse valueForKey:#"authentication_token"];
//NSLog(#"response: [%#] %#", [parsedResponse class], parsedResponse);
if (token.length > 0) {
NSLog(#"response status: %d, token: %#", response.statusCode, token);
[[RKClient sharedClient] setValue:token forHTTPHeaderField:#"X-Rabatme-Auth-Token"];
if (loadBlock) loadBlock(response);
}
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
request.onDidFailLoadWithError = failBlock;
}];
}
You should also take a look at this SO question: RestKit GET query parameters.
I just installed the framework restkit 0.9.3 and followed the Discussion Board example. Well, everything just worked great, however when I tried to use Core Data my User NSManagedObject class is duplicating even after declaring his primaryKeyAttribute (userID). For example, when I send a login request to my web-server, I return {"user":{"id":1, "username":"teste", ...}} .. but it seems to create a new row every time it invoques objectLoader:didLoadObjects.
User table:
Example code:
~ AppDelegate.m didFinishLaunching
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
userMapping.primaryKeyAttribute = #"userID";
userMapping.setDefaultValueForMissingAttributes = YES; // clear out any missing attributes (token on logout)
[userMapping mapKeyPathsToAttributes:
#"id", #"userID",
#"email", #"email",
#"username", #"username",
#"password", #"password",
nil];
[objectManager.mappingProvider registerMapping:userMapping withRootKeyPath:#"user"];
~ User.m loginWithDelegate
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
~ User.m didLoadObjects (RKObjectLoaderDelegate)
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/login"]) {
[self loginWasSuccessful];
}
NSLog(#"number of user rows: %i", [User findAll].count);
}
What am I doing wrong?
Are you correctly implementing RKManagedObjectCache? For debugging I had it simply return nil and forgot about that. A little while later I found I had duplicates also.
The cache works by fetching local objects and comparing with server returned objects. Any local objects that are not in the server response will be deleted. In earlier versions it used a fetch request but in newer versions you must manually perform the request and return actual objects.
If you return nil, it thinks this object is not in your cache and will add a duplicate. Try implementing this method:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
For example:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
[request setFetchLimit: 1];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", primaryKeyAttribute, primaryKeyValue]];
NSArray *results = [NSManagedObject executeFetchRequest:request inContext: managedObjectContext];
if ([results count] == 0)
{
return nil;
}
return [results objectAtIndex:0];
}
I have found the problem related to targetObject (RKObjectLoader)
/**
* The target object to map results back onto. If nil, a new object instance
* for the appropriate mapping will be created. If not nil, the results will
* be used to update the targetObject's attributes and relationships.
*/
So when I set it to nil the postObject calls findOrCreateInstanceOfEntity:withPrimaryKeyAttribute:andValue
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.targetObject = nil;
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.