Synchronous calling web API service for a generic method - restkit

I am using RestKit version 0.20.3 to make a generic method that is used in many other places. The problem is the returned value from that method is always nil because the "return location;" statement is executed BEFORE the Success call back function over the [objectManager getObjectsAtPath ...] method (see below codes).
I want the "return location;" statement must WAIT for the block variable "location" is filled with data from the Success call back function inside the [objectManager getObjectsAtPath ...] method. How can I do this?
Thank you for your help.
My generic method looks like:
-(KNSunGoogleLatitudeLongitudeGeometryLocation*)getSynchronouslyLatitudeLongitudeWithAddress:(NSString*)address
{
__block KNSunGoogleLatitudeLongitudeGeometryLocation* location = [[KNSunGoogleLatitudeLongitudeGeometryLocation alloc] init];
NSURL *baseURL = [NSURL URLWithString:#"http://maps.googleapis.com/maps/api"];
AFHTTPClient * client = [AFHTTPClient clientWithBaseURL:baseURL];
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
//1. KNSunGoogleLatitudeLongitudeGeometryLocation
RKObjectMapping *locationMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitudeGeometryLocation class]];
[locationMapping addAttributeMappingsFromArray:#[#"lat", #"lng"]];
//2. KNSunGoogleLatitudeLongitudeGeometry
RKObjectMapping *geometryMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitudeGeometry class]];
//3. KNSunGoogleLatitudeLongitude
RKObjectMapping *latLongMapping = [RKObjectMapping mappingForClass:[KNSunGoogleLatitudeLongitude class]];
//4. property/relationship mapping
[geometryMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"location"
toKeyPath:#"location"
withMapping:locationMapping]];
[latLongMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"geometry"
toKeyPath:#"geometry"
withMapping:geometryMapping]];
// 6. response
RKResponseDescriptor * responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:latLongMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"results"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// 7
[objectManager addResponseDescriptor:responseDescriptor];
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:address, #"address", #"false", #"sensor", nil];
// 6
[objectManager getObjectsAtPath:#"http://maps.googleapis.com/maps/api/geocode/json"
parameters:queryParams
success:^(RKObjectRequestOperation * operaton, RKMappingResult *mappingResult)
{
//-----------
NSArray* results = [mappingResult array];
KNSunGoogleLatitudeLongitude* result0 = [results objectAtIndex:0];
KNSunGoogleLatitudeLongitudeGeometry* geometry = result0.geometry;
location= geometry.location;
NSLog(#"lat=%#, long=%#", location.lat, location.lng);
}
failure:^(RKObjectRequestOperation * operaton, NSError * error)
{
NSLog (#"failure: operation: %# \n\nerror: %#", operaton, error);
}];
return location; // note: ALWAYS RETURNs nil
}

You need to change what you want because it's a bad design. You should not block the requestor while the request is in progress. Instead you should pass a block to your general method that is executed from the block you pass to RestKit. This allows you to properly respect the asynchronous nature of the request.
If you did want to proceed with blocking, you could use look at using a semaphore. But, you would need to manage this yourself. And you wouldn't be able to trigger the request on the main thread - ever. These are significant hurdles to general usage and will probably cause you issues in the future.

Related

NSURLSession request HTTPBody becomes nil in Custom NSURLProtocol

In my application, I am performing POST using NSURLSession.
Steps followed:
Setting Header
Setting HTTPBody
Making POST request using NSURLSession.
The code is:
NSDictionary *parameters = #{ #"searchTerm": #"shampoo", #"sort": #"Most Reviewed" };
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:NSJSONWritingPrettyPrinted error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"SomeURL"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:#"POST"];
[request setAllHTTPHeaderFields:headers];
request.HTTPBody = postData;
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSLog(#"Pass");
}
}];
[dataTask resume];
Now in custom NSURLProtocol class:
(BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([request.HTTPMethod isEqualToString:#"POST"]) {
//here request.HTTPMethod is coming nil
//Whereas my requirement is to get request.HTTPMethod which got request parameter.
return YES;
}
return NO;
}
Thanks in advance.
IIRC, body data objects get transparently converted into streaming-style bodies by the URL loading system before they reach you. If you need to read the data:
Open the HTTPBodyStream object
Read the body data from it
There is one caveat: the stream may not be rewindable, so don't pass that request object on to any other code that would need to access the body afterwards. Unfortunately, there is no mechanism for requesting a new body stream, either (see the README file from the CustomHTTPProtocol sample code project on Apple's website for other limitations).

Xcode, UIImage is NIL when i trying to implement Load image asynchronously

I trying to implement Load image asynchronously.
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage *getImg = [[UIImage alloc] initWithData:data];
// do whatever you want with image
}
}];
But when i put this code, getImg will get warning "Unused Variable". i had check "response", "data" and "error", it all look fine but the getImg is NIL. is that i had write any thing wrong? thanks.
The affected variable is response. Although you use data and error, response is only declared as a parameter but nowhere used in your completion handler!
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
NSString *errorMsg = nil;
UIImage *getImg = nil;
if (!error){
getImg = [[UIImage alloc] initWithData:data];
}
else
{
errorMsg = [NSString stringWithFormat:#"Failed to load image. Error Message: %#", [error localizedString]];
}
[self handleImageRequestWithResponse:response image:getImg andErrorMessage:errorMsg];
}];
// Image hasn't load yet here since the request is asynchronously!
//if(getImg != nil && errorMsg == nil)
// NSLog(#"Image is available!");
//else
// NSLog(#"Loading the image asynchronously failed! %#", errorMsg);
// In addition now provide the following method.
- (void) handleImageRequestWithResponse:(NSURLResponse*)response image:(UIImage*)img andErrorMessage:(NSString*)err
{
if(img!= nil && err == nil)
NSLog(#"Image is available!");
else
NSLog(#"Loading the image asynchronously failed! %#", err);
// Handle image
};
EDIT: My bad! Since the code executes asynchronously getImg was of course nil when you checked as before
EDIT:
Using NSData dataWithContentsOfURL is synchronous,i.e. if executed on the main thread your application is blocked.
See this official documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/clm/NSData/dataWithContentsOfURL:
Most important:
Important: Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Going for a completionHandler and a handler method called after the requested raw data has been handled/prepared is better for your performance and does not violate the official recommendation!

AFNetworking are not writing a response in variable

I have a problem code with using AFNetworking:
#import "SyncProfile.h"
#import "AFNetworking.h"
#implementation SyncProfile: NSObject
#synthesize delegate = _delegate;
- (BOOL)syncProfile {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *token =[userDefaults objectForKey:#"token"];
int user_id = [userDefaults objectForKey:#"user_id"];
if([token length]) {
self.profileData = [[self sendRequest:#"method.get" token:token withUser:user_id andParameters:#"param1,param2"] valueForKeyPath:#"response"];
NSLog(#"%#", self.profileData);
return YES;
} else
return NO;
}
-(id)sendRequest:(NSString *)apiMethod token:(NSString *)token withUser:(int)user_id andParameters:(NSString *)param {
NSMutableString *apiLink = [NSMutableString stringWithFormat:#"https://domain.com/method/%#?uid=%#&fields=%#&access_token=%#", apiMethod, user_id, param, token];
NSURL *url = [[NSURL alloc] initWithString:apiLink];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"%#", JSON);
self.req = JSON;
[self myMethod:JSON];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
return self.req;
}
- (id)myMethod:(id)data {
NSLog(#"%#",data);
return 0;
}
#end
I need to return a variable with the result AFNetworking back method. But the result is given much later than the method returns. When I use a different method to handle the result, it does not. Tried to use the [operation waitUntilFinished] but nothing has changed.
Result in Xcode Output:
//Return variable from "sync" method
2013-02-26 23:57:29.793 walkwithme[13815:11303] (null)
//Return from AFN response
2013-02-26 23:57:31.063 walkwithme[13815:11303] {response = ({someJSON})}
//Return from MyMethod
2013-02-26 23:57:31.063 walkwithme[13815:11303] {response = ({someJSON})}
You definitely don't want to use any wait methods. What you need to do is have a call back in your success and failure blocks. You can do this the way I showed in this question you could also do something else like message passing. The key thing to realize is you won't be using the typical method return pattern. Reason being with asynchronous methods like this you have no idea when it will finish which is why it uses the block call backs. Like I said you definitely don't want to wait because that could entirely block your application.
EDIT:
I use this code in one of my projects:
Declare method
- (void)postText:(NSString *)text
forUserName:(NSString *)username
ADNDictionary:(NSDictionary *)dictionary
withBlock:(void(^)(NSDictionary *response, NSError *error))block;
Then inside this method I pass those parameters to the network request.
Return values in the block with:
if (block) {
block(responseObject, someError);
}
Then I call it with this:
[[KSADNAPIClient sharedAPI] postText:postText
forUserName:username
ADNDictionary:parameters
withBlock:^(NSDictionary *response, NSError *error)
{
if (error) {
// Deal with error
} else {
// Probably success!
}
}
This way the called method returns it's values to the caller method inside the block. I think about it that it defers the block to the caller.

SKProductRequest synchronous

i start the product request already in a operation block
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
SKProductsRequest * request= [[SKProductsRequest alloc] initWithProductIdentifiers: selectedSKIdentifiers];
request.delegate = self;
[request start];
}];
so i would like to use some synchronous version, like when downloading data
NSData * data = [[NSData alloc] initWithContentsOfURL:url];
or parsing XML is also synchronous
NSXMLParser * parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[parser release];
Is there a way for to do the SKProductsRequest synchronous
The apple documentation luck on further info on this topic at this day
i found a way how to make any method synchronous :
// create the object that will perform an async operation
MyConnection *conn = [MyConnection new];
// create the semaphore and lock it once before we start
// the async operation
self.theLock = [NSConditionLock new];
// start the async operation
self.testState = 0;
[conn doItAsyncWithDelegate:self];
// now lock the semaphore - which will block this thread until
// [self.theLock unlockWithCondition:1] gets invoked
[self.theLock lockWhenCondition:1];
// we're done
[self.theLock release];
[conn release];
Make sure to invoke!!!!!!!!!!!!!!!!!
[self.theLock unlockWithCondition:1];
In the delegate(s) then.

Cocoa Core Data: Setting default entity property values?

I know I can set default values either in the datamodel, or in the -awakeFromInsert method of the entity class. For example, to make a "date" property default to the current date:
- (void) awakeFromInsert
{
NSDate *now = [NSDate date];
self.date = now;
}
How though can I make an "idNumber" property default to one greater than the previous object's idNumber?
Thanks, Oli
EDIT: Relevant code for my attempt (now corrected)
- (void) awakeFromInsert
{
self.idNumber = [NSNumber numberWithInt:[self maxIdNumber] + 1];
}
-(int)maxIdNumber{
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Flight" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idNumber > %#", [NSNumber numberWithInt:0]];
[request setPredicate:predicate];
[request setFetchLimit:1];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"idNumber" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil | array.count == 0)
{
return 0;
}
return [[[array objectAtIndex:0] valueForKey:#"idNumber"] intValue];
}
If the maxIdNumber method is called, the new object is added to the table twice!? (but with the correct idNumber). The two entries in the table are linked - editing / removing one also edits / removes the other. For this reason I believe it has something to do with the managed object context. For what its worth, the outcome (two copies) is the same no matter how many times the maxIdNumber method is called in the awakFromNib; even if self.idNumber is just set to [NSNumber numberWithInt:5] and the maxIdNumber method is just called for a throwaway variable.
Any clues??
SOLVED IT!
Ok, the problem of double entry occurs when a fetch request is performed from within the awakeFromInsert method. Quoting from the docs:
You are typically discouraged from performing fetches within an implementation of awakeFromInsert. Although it is allowed, execution of the fetch request can trigger the sending of internal Core Data notifications which may have unwanted side-effects. For example, on Mac OS X, an instance of NSArrayController may end up inserting a new object into its content array twice.
A way to get around it is to use the perfromSelector:withObject:afterDelay method as outlined here (I am only allowed to post one hyperlink :( ):http://www.cocoabuilder.com/archive/cocoa/232606-auto-incrementing-integer-attribute-in-awakefrominsert.html.
My working code is now as follows: (note, I have put the bulk of the fetching code used above into a category to tidy it up a little, this allows me to use the method fetchObjectsForEntityName:withPredicate:withFetchLimit:withSortDescriptors:)
- (void) awakeFromInsert
{
[self performSelector:#selector(setIdNumber) withObject:nil afterDelay:0];
self.date = [NSDate date];
}
-(void)setIdNumber
{
int num = 0;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"idNumber" ascending:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idNumber > %#", [NSNumber numberWithInt:0]];
NSArray *array = [[self managedObjectContext] fetchObjectsForEntityName:#"Flight"
withPredicate:predicate
withFetchLimit:0
withSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
if (array != nil & array.count != 0)
{
num = [[[array objectAtIndex:0] valueForKey:#"idNumber"] intValue];
}
num ++;
[self setIdNumber:[NSNumber numberWithInt:num]];
}
Let me know what you think!
One Approach: Create a fetch request of all instances of your entity with a limit of 1, sorted by idNumber to get the highest number.
Another Approach: Keep the highest idNumber in your store's metadata and keep incrementing it.
There are plenty of arguments for and against either. Ultimately, those are the two most common and the choice is yours.
An easier way to do that is to override the newObject method of NSArrayController:
- (id) newObject
{
id result=[super newObject];
[result setValue: [NSDate date] forKey: #"date"];
return result;
}

Resources