NSURLConnection sendAsynchronousRequest with retry functionality - nsurlconnection

I have my main UI thread that calls sendAsynchronousRequest method of NSURLConnection to fetch data.
[NSURLConnection sendAsynchronousRequest:[self request]
queue:[NSOperationQueue alloc] init
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
//error handler
}
else
{
//dispatch_asych to main thread to process data.
}
}];
All this is fine and good.
My question here is, I need to implement retry functionality on error.
Can I do it in this block and call sendSynchronousRequest to retry as this is the background queue.
Or dispatch to main thread and let the main thread handle retry (by calling sendAsynchronousRequest and repeating the same cycle).

You're getting the request by calling [self request]. If request is an atomic #property, or is otherwise thread safe, I can't think of any reason you couldn't kick off a retry from a non-main thread.
Alternately, you could put a copy of the request into a local variable prior to your +sendAsynchronousRequest:queue: call. If you do that, and then reference it in your completion handler, then it will be retained implicitly and [self request] will only be called once.
Generally speaking, this probably isn't a great pattern. If the service is down, absent some other checks, it will just keep trying forever. You might try something like this:
NSURLRequest* req = [self request];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
__block NSUInteger tries = 0;
typedef void (^CompletionBlock)(NSURLResponse *, NSData *, NSError *);
__block CompletionBlock completionHandler = nil;
// Block to start the request
dispatch_block_t enqueueBlock = ^{
[NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:completionHandler];
};
completionHandler = ^(NSURLResponse *resp, NSData *data, NSError *error) {
tries++;
if (error)
{
if (tries < 3)
{
enqueueBlock();
}
else
{
// give up
}
}
else
{
//dispatch_asych to main thread to process data.
}
};
// Start the first request
enqueueBlock();

Related

XCTest: NSURLSession: Stall on main thread

When running an XCTest that executes a network operation, i get the following error:
-waitForExpectationsWithTimeout: timed out but was unable to run the timeout handler because the main thread was unresponsive (0.5 seconds is allowed after the wait times out). Conditions that may cause this include processing blocking IO on the main thread, calls to sleep(), deadlocks, and synchronous IPC. Xcode will attempt to relaunch the process and continue with the next test...
I have a test that executes a Network Command method which uses NSURLSession under the hood.
This test is part of a test class with 4 consecutive network request tests.
When run independently from other tests it succeeds every time.
When running the entire Test file (4 tests in total), it succeeds every time.
But when running the entire test suite (50 tests), i get "Stall on main thread" on the first network operation test. The other 3 network operation tests go through fine.
Here is the code for the test:
- (void)test_session_001_ObtainsSessionObjectFromServer
{
XCTestExpectation *expectation = [self expectationWithDescription:[self description]];
id<IPVideoSessionProtocol> provider = [IPVideoSessionFactory getProvider];
[provider createVideoSessionWithUserID:#"SomeUserID"
withCompletionBlock:^(IPVideoSession *session) {
if ([session isKindOfClass:[IPVideoSession class]] &&
[session.sessionID isKindOfClass:[NSString class]] &&
session.sessionID.length > 0) {
// The returned session ID is populated
[expectation fulfill];
}
}];
[self waitForExpectationsWithTimeout:5.0f handler:^(NSError *error) {
if (error)
{
XCTFail(#"IPVideoSessionManagerTests Did not execute in allotted time");
}
}];
}
The code inside createVideoSession...
- (void)createVideoSessionWithUserID:(NSString*)userID
withCompletionBlock:(IPVideoSessionCompletionBlock)block
{
IPCreateVideoSessionCommand* command = [[IPCreateVideoSessionCommand alloc]
initWithUserID:userID];
[command executeWithCompletionBlock:^(IPNetworkError *error, NSString *resultJSON)
{
if (error) {
[IPAlertPresenter displayNetworkError:error];
block(nil);
return;
}
NSDictionary *dict = [resultJSON toJsonDictionary];
block([IPVideoSession getSessionFromDictionary:dict]);
}];
}
The code inside executeWithCompletionBlock:
- (void)executeWithCompletionBlock:(CommandCompletionBlock)block
{
if (!self.isConnected && ![[IPDebugManager sharedInstance] networkBypassActivated]) {
IPNetworkError* error = [[IPNetworkError alloc] initWithType:NetworkErrorTypeNotConnectedToNetwork];
block(error, nil);
return;
}
NSURLSession *session = [self composeSession];
NSURLRequest *request = [self composeRequest];
self.strongSelf = self;
__weak __typeof(self) weakSelf = self;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[IPDispatch toMainThread:^{
[weakSelf
handleCommandResponse:response
withData:data
withError:error
withCompletionBlock:block];
weakSelf.strongSelf = nil;
}];
}];
[dataTask resume];
}
NOTE: This error only happens when all tests are run. This test succeeds by itself, and also: if I run all 4 tests inside this Test Class.

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.

TWRequest code works but very slow to show?

I'm working with TWrequest to display my twitter lists in a tableview. The following code works. The problem is it is very slow to update the table. I am NSlogging the request response (which happens very quickly), I am also looping through each list and adding the list 'name' to an array (which again, happens very quickly <1s). But for some inexplicable reason, the table takes roughly a further 4 seconds or so to update.
Why is this taking so long for the table to reload? The problem is not parsing the response (because I can see with nslog this happens pretty quick), it's taking a long time to display in the table? Help very much appreciated!
-(IBAction)getLists{
// First, we need to obtain the account instance for the user's Twitter account
ACAccountStore *store = [[ACAccountStore alloc] init];
ACAccountType *twitterAccountType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request permission from the user to access the available Twitter accounts
[store requestAccessToAccountsWithType:twitterAccountType withCompletionHandler:^(BOOL granted, NSError *error) {
if (!granted) {
// The user rejected your request
NSLog(#"User rejected access to the account.");
}
else {
// Grab the available accounts
twitterAccounts = [store accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] > 0) {
// Use the first account for simplicity
ACAccount *account = [twitterAccounts objectAtIndex:0];
// Now make an authenticated request to our endpoint
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
//[params setObject:#"1" forKey:#"include_entities"];
// The endpoint that we wish to call
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1.1/lists/list.json"];
// Build the request with our parameter
TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:params requestMethod:TWRequestMethodGET];
// Attach the account object to this request
[request setAccount:account];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (!responseData) {
// inspect the contents of error
NSLog(#"error = %#", error);
}
else {
NSError *jsonError;
NSArray *timeline = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError];
if (timeline) {
// at this point, we have an object that we can parse
NSLog(#"timeline = %#", timeline);
for (NSDictionary *element in timeline) {
NSString *listName = [element valueForKey:#"name"];
[listsArray addObject:listName];
}
[listsTable reloadData];
}
else {
// inspect the contents of jsonError
NSLog(#"jsonerror = %#", jsonError);
}
}
}];
}
}
}];
}
Sorry, just came across this post. If you haven't found a solution yet, hopefully this will help.
I believe that performRequestWithHandler can be called on any thread, so UI changes should be dispatched to the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
});
Or in the case of reloading table data you can use:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];

How can I wait for result from geocodeAddressString iPhone

I know its something to do with locks or dispatch groups, but I just cant seem to code it...
I need to know if the address was a valid address before leaving the method. Currently the thread just overruns and returns TRUE. I've tried locks, dispatchers the works but can't seem to get it correct. Any help appreciated:
- (BOOL) checkAddressIsReal
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
}];
return result;
}
The documentation says that CLGeocoder calls the completionHandler on the main thread. Since you are probably also calling your method from the main thread it cannot wait for the geocoder's answer without giving it the opportunity to deliver the result.
That would be done by polling the runloop, using some API as -[NSRunLoop runMode:beforeDate:].
The disadvantage is that depending on the mode this will also deliver events and fire timers while waiting for the result.
Just use block as parameter:
- (void) checkAddressIsRealWithComplectionHandler:(void (^)(BOOL result))complectionHandler
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
complectionHandler(result);
}];
}

Resources