Objective-c + RestKit - Wait for response before next step - ios

Hello and thanks in advance for your help. I've been looking but couldn't find an answer for this. I've only been programing for iOS for a week.
So far, all the connections to the web services are functioning and I've created a class and methods to do those calls. I'm making a standard login, user enters login info, the app passes those values to the web service and it returns a bool value depending if the info matches anything on the database. After that, the app gets the return value and it moves to the next screen or shows an error alert. Or at least that's what I'm aiming for.
The problem is that, the conditional is being executed before the rest call is made or the response parsed and I'm not having much luck finding a solution. I've read about asynchronous and synchronous calls but hadn't have much luck at implementing them.
This is the call code:
//Class that has the methods for calling the web services
__restObj = [[restCalls alloc] init];
bool login = [__restObj restLogin:user passwd:pass];
if (login) {
[self performSegueWithIdentifier:#"adminLogin" sender:self];
}
else{
UIAlertView*alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Incorrect group name or password." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
}
The conditional in being performed before the actual POST occurs and there for is always false.
This is my method:
- (bool)restLogin:(NSString*)user passwd:(NSString*)pass{
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
groupInfo *gi = [[groupInfo alloc] init];
gi.gName = user;
gi.pass = pass;
RKObjectMapping *userInfoMapping = [RKObjectMapping requestMapping];
[userInfoMapping addAttributeMappingsFromDictionary:#{#"gName": #"groupName",#"pass":#"pass"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userInfoMapping
objectClass:[groupInfo class]
rootKeyPath:nil];
[objectManager addRequestDescriptor:requestDescriptor];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager postObject:gi
path:#"adminLoginIos"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray* statuses = [mappingResult array];
NSLog(#"Loaded statuses: %#", statuses);
_result = [statuses objectAtIndex:0];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Hit error: %#", error);
}
];
return _result;
}
Thanks in advance and keep in mind that I'm really new at this so I appreciate your help and if my code is not the best please tell me so.
Regards,
ChmlGr

You need to pass in a block and then inside the success callback, block return the _result.
An example based on your structure would be something like:
-(void) restLogin:(NSString*)user passwd:(NSString*)pass block:(void (^)(id))block {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
groupInfo *gi = [[groupInfo alloc] init];
gi.gName = user;
gi.pass = pass;
RKObjectMapping *userInfoMapping = [RKObjectMapping requestMapping];
[userInfoMapping addAttributeMappingsFromDictionary:#{#"gName": #"groupName",#"pass":#"pass"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userInfoMapping
objectClass:[groupInfo class]
rootKeyPath:nil];
[objectManager addRequestDescriptor:requestDescriptor];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager postObject:gi
path:#"adminLoginIos"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray* statuses = [mappingResult array];
NSLog(#"Loaded statuses: %#", statuses);
_result = [statuses objectAtIndex:0];
block(_result);
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Hit error: %#", error);
block(nil);
}
];
}
Your call to that method would be something like:
[__restObj restLogin:user passwd:pass block:^(id obj) {
// do something here to translate that object into a BOOL and check value
}];
I don't use RestKit, so I can't verify this is exactly what you need, but it should get you on the right path. That said, if you wanted to check out AFNetworking, I wrote a NetworkClient wrapper that I don't mind sharing.

Related

How to Get Object based response in Restkit 0.20.3

Hi have used restkit in my several previous projects with version 0.10.0. But now i am going to move with new restkit v0.20.3.
I followed all the steps from upgrading 10.0 to 20.0 from HERE.
I am able execute my request and response also came under success blog. But i can get the property of my response object. That is very shocking for now. I can get value only by [data valueForKey:#""] which is not good i guess in restkit.
Can any one tell me about how we get value from object's property.
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"%lu", (unsigned long)mappingResult.array.count);
DataForResponse *data = [mappingResult.array objectAtIndex:0];
User *user = [data valueForKey:#"user"];
User *user = [[data.user allObjects] firstObject];
NSLog(#"%#",[user valueForKey:#"email"]);
RKLogInfo(#"Load collection of Users: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"%#",operation.HTTPRequestOperation.responseString);
RKLogError(#"Operation failed with error: %#", error);
}];
My goal is to get value of email like from user.email
After searching & based on my restkit knowledge below code is worked.
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[DataForResponse objectMappingForDataResponse:LOGIN] method:RKRequestMethodPOST pathPattern:nil keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[rkomForLogin addResponseDescriptor:responseDescriptor];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor ]];
operation.targetObject = data;
[rkomForLogin postObject:nil path:#"login" parameters:dict success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// Handled with articleDescriptor
NSLog(#"%#",operation.HTTPRequestOperation.responseString);
DataForResponse *data = [mappingResult.array objectAtIndex:0];
User *user = [[data.user allObjects] firstObject];
NSLog(#"%#",[user email]);
NSLog(#"%ld",operation.HTTPRequestOperation.response.statusCode);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Transport error or server error handled by errorDescriptor
NSLog(#"%#",operation.HTTPRequestOperation.responseString);
RKLogError(#"Operation failed with error: %#", error);
}];
This is same object based response that i was getting in restkit 0.10 so this is way to execute a request.

How to save to Core Data RKMappingResult object in RestKit?

I try to save to Core Data NSManagedObject which I got from server. But I don't know any idea how to save object got from [mappingResult firstObject] in success block to Core Data. How can I do this? Should I use RKObjectManager's postObject or RKManagedRequestOperation? Should I do [managedObjectContext insertNewObjectForEntityForName:#""] before this?I can't find any instructions in official docs for this case and need some help.
**EDIT: **I initialise RKManagedRequestOperation like this:
RKResponseDescriptor* responseDescriptor =[RKResponseDescriptor
responseDescriptorWithMapping:[UserMapping mappingForUser]
method:RKRequestMethodPOST
pathPattern:kUserEndpoint
keyPath:#"profile"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKRequestDescriptor* requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[UserMapping mappingForUserProfileModel]
objectClass:[User class] rootKeyPath:#"profile" method:RKRequestMethodPOST];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
userObject = [User new];
NSDictionary* userParameters = #{ #"user_id" : [User sharedUser].userId};
[[RKObjectManager sharedManager] appropriateObjectRequestOperationWithObject:resumeObject method:RKRequestMethodPOST
path:kUserEndpoint
parameters:userParameters];
RKManagedObjectRequestOperation* managedRequest = [[RKManagedObjectRequestOperation alloc]
initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#", kService, kUserEndpoint]]]
responseDescriptors:#[responseDescriptor]];
managedRequest.managedObjectContext = _managedObjectContext;
[managedRequest setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"MAPPING = %#", [mappingResult firstObject]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:managedRequest];
EDIT2: RestKit doesn't save mapped data to CoreData. But userObject.title saves perfectly:
userObject = [_managedObjectContext insertNewObjectForEntityForName:#"User"];
userObject.title = #"USER_NAME";
NSDictionary* userParameters = #{ #"user_id" : [User sharedUser].userId};
[[RKObjectManager sharedManager] postObject:userObject path:kUserEndpoint parameters:userParameters
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) { }];
You need to take a step back as your suggestions are shooting in the dark.
If you configure your object manager with a managed object store and create response descriptors with entity mappings then when you receive data as a result of requests this will be converted into managed objects. These objects will automatically be saved to the core data store before the success block is called.
Any other objects you want to create can be created as usual and you need to explicitly save the context.
Sending requests with RestKit doesn't itself change the store contents, only the response results in changes.
See this tutorial. There is an example of using RestKit with object management and Code Data
https://github.com/alexanderedge/RestKitTutorial1

Restkit - Post object

I try to post an object Foobar (the class has two attributes: string foo and string bar) using the method postObject of the class RKObjectManager.
Server-side, I have a WCF service that receive the POST method
public void PostFoobar(Foobar foobar) { ... }
All the connection works. The problem is that the object foobar received is always NULL. It seems ResKit does not POST my object as an encapsulation of the two arguments, but post two string independently.
I mean, when I tryed to implement the following method (server-side) :
public void PostFoobar(string foo, string bar) { ... }
and the two parameters was not null ! it has worked !
But I would prefer to recover the serialized object obviously...
My question is :
How am I suppose to configure my POST request to recover an entire Foobar object on the server side, and not every attributes independently ?
My code
Here is my code to send the POST request
NSError *error = nil;
NSManagedObjectModel *managedObjectModel = #"myObjecModel";
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[RKManagedObjectStore setDefaultStore:managedObjectStore];
// Configure the object manager
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://192.168.1.10/rest"]];
objectManager.managedObjectStore = managedObjectStore;
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
[RKObjectManager setSharedManager:objectManager];
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"text/plain"];
RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:#"Foobar" inManagedObjectStore:managedObjectStore];
[postMapping addAttributeMappingsFromDictionary:#{
#"foo" : #"strFoo", // server side:foo, iOS side: strFoo
#"bar" : #"strBar" // server side:bar, iOS side: strBar
}];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[postMapping inverseMapping] objectClass:[Foobar class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
// POST to create
RKManagedObjectStore *objectStore = [[RKObjectManager sharedManager] managedObjectStore];
Foobar *foobar = [NSEntityDescription insertNewObjectForEntityForName:#"Foobar" inManagedObjectContext:objectStore.mainQueueManagedObjectContext];
foobar.strFoo = #"foo ipad";
foobar.strBar = #"bar ipad";
#try {
[[RKObjectManager sharedManager] postObject:foobar path:#"foobar" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", error.localizedDescription);
}];
}
#catch (NSException *exception) {
NSLog(#"error - %#", exception);
}
One more thing :
I have tested my web service with the chrome client "Simle REST client" and it works with the data :
{ "foo": "foo from chrome", "bar" : "bar from chrome" }
Edit - Frame capture with Wireshark
It seems to be good.. I don't understant.
You're calling with nil:
[[RKObjectManager sharedManager] postObject:nil path:#"foobar" parameters:nil ...
So RestKit is given nothing to serialise and no parameters.
If that's a typo, turn on trace logging for mapping and look at what it's doing. Also, get Charles or a similar tool and check exactly what data is being sent over the network.

writing an API class for RestKit framework

I switched from AFnetworking to RestKit. In AFnetworking had an API class. The API.h class contained the following.
#import <UIKit/UIKit.h>
typedef void (^JSONResponseBlock)(NSDictionary* json);
#interface API : NSObject
//the authorized user
#property (strong, nonatomic) NSDictionary* user;
+(API*)sharedInstance;
//check whether there's an authorized user
//send an API command to the server
-(void)loginCommand:(NSMutableDictionary*)params onCompletion:(JSONResponseBlock)completionBlock;
And my API.m class looks like this.
+(API *)sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^ {
sharedInstance = [[self alloc]initWithBaseURL:[NSURL URLWithString:kAPIHost]];
});
return sharedInstance;
}
#pragma mark - init
//intialize the API class with the destination host name
-(API *)init
{
//call super init
self = [super init];
if (self != nil){
//initialize the object
user = nil;
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
-(void)loginCommand:(NSMutableDictionary *)params onCompletion:(JSONResponseBlock)completionBlock{
NSLog(#"%#%#",kAPIHost,kAPILogin);
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST" path:kAPILogin parameters:params constructingBodyWithBlock:^(id <AFMultipartFormData>formData){
//TODO: attach file if needed
}];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject){
//success!
NSLog(#"SUCCESSSS!");
completionBlock(responseObject);
}failure:^(AFHTTPRequestOperation *operation, NSError *error){
//Failure
NSLog(#"FAILUREE!");
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
Like you can see I only instantiate it once and put all my methods in over here. In my view controller I only need to call this method with a parameter-dictionary. Then I could read the whole JSON file.
Now with restKit I do this all on viewController level. I want to split it up like I did by AFNetworking. This is what I do in RestKit, At the moment is this all on viewController level.
//let AFNetworking manage the activity indicator
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
// Initialize HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://virtuele-receptie.preview.sanmax.be"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
//we want to work with JSON-Data
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
//Do mapping
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dataMapping
pathPattern:nil
keyPath:#"data"
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
NSDictionary *dict = [[NSDictionary alloc]initWithObjectsAndKeys:_txtLogin.text,#"email",_txtPass.text,#"pwd", nil];
[objectManager getObject:nil path:#"/nl/webservice/company-user/login/apikey/key12345678" parameters:dict
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//Success
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
//Failure
}];
So far with RestKit I haven't seen a huge need for an API class like you might create with other HTTP frameworks. RestKit has its own HTTP client (actually, just AFNetworking's client), so there's no need to have a class for your HTTP client, and I've found that each time I use the RKObjectManager I generally want access to the method parameters and block callbacks within each view controller. In other words, I don't want to run RestKit networking code in an API class, because I would essentially have to wrap the entire call in a method that can be accessed in the view controller (success block, failure block, etc).
In essence, RestKit's design lightens the networking code so much that in my experience with it - 3 or 4 apps now - I've yet to see enough reason write an API class like you describe.

Rails 3.2.8 + RestKit - Mapping without KVC

So I am trying to grab information in JSON from my rails app with RestKit
My code to do so is like so:
App Delegate
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Initialise RestKit
NSURL *URL = [NSURL URLWithString:#"https://myapp.dev"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:URL];
//Enable Activity Indicator Spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *eventMapping = [RKObjectMapping mappingForClass:[Info class]];
[infoMapping addAttributeMappingsFromDictionary:#{
#"sample":#"sample",
#"sample_1":#"sample_1"
}];
RKResponseDescriptor *infoDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:infoMapping
pathPattern:#"/info"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:infoDescriptor];
}
View File
- (void)loadInfo
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager getObjectsAtPath:#"/info"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *info = [mappingResult array];
NSLog(#"Loaded info: %#", info);
_info = info;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error getting into"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
The problem is. On the log output, RestKit tells me it maps everything successfully. But then when I attempt to view the object with both the method to log in the view file and with the debugger using po I get the following
374 Finished performing object mapping. Results: {
"<null>" = "<Info: 0xa291b30>";
}
I can't view the object and with breakpoints it shows up as:
I've been struggling with this for a few days and I'm not sure what else to try. Any help would be greatly appreciated
I ran into a similar problem. I'm not too sure why, but when I did the following it fixed it.
- (void)loadInfo
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager getObjectsAtPath:#"/info"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *info = [mappingResult array];
NSLog(#"Loaded info: %#", info);
_info = info;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error getting into"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Initialise RestKit
NSURL *URL = [NSURL URLWithString:#"https://myapp.dev"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:URL];
//Enable Activity Indicator Spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *eventMapping = [RKObjectMapping mappingForClass:[Info class]];
[infoMapping addAttributeMappingsFromDictionary:#{
#"sample":#"sample",
#"sample_1":#"sample_1"
}];
RKResponseDescriptor *infoDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:infoMapping
pathPattern:#"/info/:id"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:infoDescriptor];
}
When you load an object representation that does not have a nesting keyPath, RestKit stores the mapped objects under the [NSNull null] key within the dictionary (since nil is not a valid dictionary key). You can retrieve the mapping results either by calling firstObject, array, or dictionary on the RKMappingResult object to access the mapped objects.
I see a follow-up question about mapping an array to a single object... what does your JSON look like and how are you trying to represent it?
RKResponseDescriptor *infoDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:infoMapping
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

Resources