I'm trying to make an asynchronous NSURL Request, but I'm getting all "FALSE."
-(BOOL)checkConnectionForHost:(NSString*)host{
BOOL __block isOnline = NO;
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:1];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([(NSHTTPURLResponse*)response statusCode]==200){
isOnline = TRUE;
}
}];
NSLog(#"%i",isOnline);
return isOnline;
}
Also, this code is being called "6" times when I'm actually just using it with a:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
and there are only 3 cells, or 3 items in my data source. First time dealing with async and callbacks in Objective-C, so a detailed answer would be much appreciated! Thanks!
Asynchronous calls will be executed in parallel, and its result will receive in the completion block. In your case, the return statement will be executed before the completion of the Asynchronous request. That will be always FALSE.
You should use Synchronous request for this, and handle not to Block the UI.
-(BOOL)checkConnectionForHost:(NSString*)host{
BOOL isOnline = NO;
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:1];
NSHTTPURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response status Code : %d",response.statusCode);
isOnline = response.statusCode == 200;
return isOnline;
}
You can use that method inside dispatch queues,
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
BOOL status = [self checkConnectionForHost:#"http://google.com"];
NSLog(#"Host status : %#",status ? #"Online" : #"Offline");
});
You should realize that this problem is inherently asynchronous. You can't solve it with a synchronous approach. That is, your accepted solution is just an elaborated and suboptimal wrapper which ends up being eventually asynchronous anyway.
The better approach is to use an asynchronous method with a completion handler, e.g.:
typedef void (^completion_t)(BOOL isReachable);
-(void)checkConnectionForHost:(NSString*)host completion:(completion_t)completionHandler;
You can implement is as follows (even though the request isn't optimal for checking reachability):
-(void)checkConnectionForHost:(NSString*)host
completion:(completion_t)completionHandler
{
NSURLRequest* request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (completionHandler) {
completionHandler(connectionError == nil && [(NSHTTPURLResponse*)response statusCode]==200);
}
}];
}
Please note:
Don't set a timeout as short as in your original code.
The completion handler will be called on a private thread.
Usage:
[self checkConnectionForHost:self.host completion:^(BOOL isReachable){
dispatch_async(dispatch_get_main_queue(), ^{
self.reachableLabel.text = isReachable ? #"" : #"Service unavailable";
});
}];
Your isOnline is probably being set to YES, but it's happening asynchronously. It is almost certainly executing after you log out the value of isOnline. So you should move your NSLog() call up into the block you pass as the handler to the asynchronous URL request.
Related
In my app I am downloading image using blocks but it is freezing my UI. I have one network class which contains method to download image,
-(void)downloadImageWithCompletionHandler:^(NSData *aData, NSError *error)aBlock;
I am calling above method in my view controller to download image. So once the image is downloaded I am using NSData to show in image view. The network class method uses NSURLConnection methods to download the image.
[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self];
Once the data download is complete I am calling completion handler block of the view controller.
But I am not sure why my UI is freezing? Can anyone help me find where I am doing wrong?
Thanks in advance!
- (void) setThumbnailUrlString:(NSString *)urlString
{
NSString *url= [NSString stringWithFormat:#"%#",urlString];
//Set up Request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setURL:[NSURL URLWithString:url]];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
if ( queue == nil ){
queue = [[NSOperationQueue alloc] init];
}
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
_headImageView.image=urlImage;
_backgroundImageView.image=urlImage;
}
});
}];
}
You need to download the image in background thread to avoid freezing the UI thread.There is a simple demo to achieve this.
- (void)downloadImageWithCompletionHandler:(void(^)(NSData *aData, NSError *error))aBlock {
NSURLRequest *theURLRequest = nil; // assign your request here.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:theURLRequest queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// UIThread.
aBlock(data,connectionError);
}];
}
how to call this method.
[self downloadImageWithCompletionHandler:^(NSData *aData, NSError *error) {
// get UIImage.
UIImage *image = [UIImage imageWithData:aData];
}];
I figured out the problem. Problem was not in the block or using NSUrlConnection method, it is working properly. Problem was, I was saving data in file once I download it. This operation was happening on main thread which was blocking the UI.
Basically I want a way to issue a NSURLRequest multiple times in a loop until a certain condition has been met. I am using a rest api but the rest api only allows up to a maximum of 1,000 results at a time. So if i have, lets say 1,500 total, i want to make a request to get the first 1,000 then i need to get the rest with another almost exact request , except the startAt: parameter is different(so i could go from 1001 - 1500. I want to set this up in a while loop(while i am done loading all the data) and am just reading about semaphores but its not working out like I expected it to. I don't know how many results I have until i make the first request. It could be 50, 1000, or 10,000.
here is the code:
while(!finishedLoadingAllData){
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];
[NSURLConnection sendAsynchronousRequest:myRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if(error){
completionHandler(issuesWithProjectData, error);
}
else{
NSDictionary *issuesDictionary = [[NSDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]];
[issuesWithProjectData addObjectsFromArray:issuesDictionary[#"issues"]];
if(issuesWithProjectData.count == [issuesDictionary[#"total"] integerValue]){
completionHandler([issuesWithProjectData copy], error);
finishedLoadingAllData = YES;
}
else{
startAt = maxResults + 1;
maxResults = maxResults + 1000;
}
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
Basically I want to keep the while loop waiting until the completion block finished. Then and only then do i want the while loop to check if we have all of the data or not(and if not, make another request with the updated startAt value/maxResults value.
Right now it just hangs on dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
What am i doing wrong or what do i need to do? Maybe semaphores are the wrong solution. thanks.
Ok. The more I look, the more I don't think its a bad idea to have semaphores to solve this problem, since the other way would be to have a serial queue, etc. and this solution isn't all that more complicated.
The problem is, you are requesting the completion handler to be run on the main thread
[NSURLConnection sendAsynchronousRequest:myRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
and you are probably creating the NSURL request in the main thread. Hence while it waits for the semaphore to be released on the mainthread, the NSURL completion handler is waiting for the mainthread to be free of its current run loop. So create a new operation queue.
would it not be easier to do something like this instead:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //run on a background thread
while(!finishedLoadingAllData){
NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:myRequest returningResponse:&response error:&error]; //blocks until completed
if(response.statusCode == 200 && responseData != nil){ //handle response and set finishedLoadingAllData when you want
//do stuff with response
dispatch_sync(dispatch_get_main_queue(), ^{
//do stuff on the main thread that needs to be done
}
}
});
Please dont do that.. NSURLConnection sendAsynchronousRequest will be loading itself in loop for you, if your data is in chunk.. try this instead..
__block NSMutableData *fragmentData = [NSMutableData data];
[[NSOperationQueue mainQueue] cancelAllOperations];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
[fragmentData appendData:data];
if ([data length] == 0 && error == nil)
{
NSLog(#"No response from server");
}
else if (error != nil && error.code == NSURLErrorTimedOut)
{
NSLog(#"Request time out");
}
else if (error != nil)
{
NSLog(#"Unexpected error occur: %#", error.localizedDescription);
}
else if ([data length] > 0 && error == nil)
{
if ([fragmentData length] == [response expectedContentLength])
{
// finished loading all your data
}
}
}];
I've created two chunky json response from server handling method.. And one of them is this, so hope this will be useful to you as well.. Cheers!! ;)
I would like to receive some data to the server in an asynchronous way and avoiding to overload the App UI performance. Hence would like to send tasks to the secondary queue and not the main one.
This is my current solution which uses the "main queue" ([NSOperationQueue mainQueue] which I understand slows down the performance):
-(NSDictionary*) fetchURL:(NSString*)url
{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:kCONTACTSINFOURL]];
__block BOOL hasError = FALSE;
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
//Verify type of connection error
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
}];
if (hasError) {
[[NSOperationQueue mainQueue] suspend];
return nil;
}
return json;
}
In order to use the secondary queue, and avoid overloading the UI and App performance, is it ok to allocate a shared NSOperationQueue and refer to that? Or is there some other "better" class or method to achieve this?
This would be my improved solution using a secondary NSOperationQueue:
Creating a secondary queue:
NSOperationQueue* otherQueue = [NSOperationQueue init];
Using the other (secondary) queue:
....
[NSURLConnection sendAsynchronousRequest:request
queue:otherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
....
}
Is this correct? Or is there any other way to deal with this?
... following on from the comments...
The NSURLConnection method
[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]
allows you to specify the queue that the completion handler will be called on when the connection is complete. If you suspend this queue you will not stop the requests being sent, you will just stop getting callbacks when they are done. You may want to think about doing this slightly differently...
NSOperation is great, but I tend to prefer going straight for GCD (NSOperation is just a nice obj-c wrapper on the top) if you are hard-set on using NSOperations let me know and i'll add some advice for that.
I assume that you have some kind of manager class that handles all of your server communication? If not, I would recommend that you do, and have it as a singleton.
# interface ChatManager : NSObject
+ (ChatManager *)sharedManager;
#end
#implementation ChatManager {
dispatch_queue_t fetchQueue;
dispatch_queue_t postQueue;
}
+ (ChatManager *)sharedManager {
// This is just the standard apple pattern for creating a singleton
static SCAddressBookManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[SCAddressBookManager alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
// Create a dispatch queue that will run requests one after another, you could make this concurrent but that may cause messages to be lost when you suspend
fetchQueue = dispatch_queue_create("fetch_queue", DISPATCH_QUEUE_SERIAL);
postQueue = dispatch_queue_create("post_queue", DISPATCH_QUEUE_SERIAL);
}
}
- (NSDictionary *)fetchURL:(NSURL *)url {
// In here we will dispatch to our queue and so that the
dispatch_async(fetchQueue, ^{
NSURLRequest = // create your request
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Check for error
if (error) dispatch_suspend(fetchQueue); // this suspends the queue that is making the calls to the server, so will stop attempting to send messages when you have an error - you should start pinging to see when you come back online here too, and then use dispatch_resume(fetchQueue) to get it going again!
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
- (NSDictionary *)postURL:(NSURL *)url data:(NSData *)bodyData {
// In here we will dispatch to our queue and so that the
dispatch_async(postQueue, ^{
/*
Make your post request.
*/
// Check for error
if (error) dispatch_suspend(postQueue);
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
#end
You may also need to use
dispatch_sync(dispatch_get_main_queue(), ^{
// Do stuff on the main thread here
};
if you need to make delegate style callbacks to the main thread!
This is a basic idea on how I would have things setup to try and achieve what you are going for... feel free to let me know if any of this doesn't make sense to you..?
As a side note, I have assumed that for whatever reason you need to have a simple http interface for your server. The much better approach would be to have a persistent socket open between the app and your server, and then you can push data up and down at will. A socket with a heartbeat would also let you know when your connection has gone down. Not sure if maybe you would like me to elaborate on this option some more...
Hi i am using following code to load the image using NSURLConnection SendAsynchronousRequest call for Tableview but it crashes for IOS 4.3 but same code works for IOS 5.
So can anyone please tell me what changes i have to do support for IOS 4.3
i have gone through below links but nothing worked for me.
NSURLConnection sendAsynchronousRequest:queue:completionHandler not working in iOS 4.3
Have a class called
imagefetcher.h
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table;
imagefetcher.m
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table {
// NOTE: url is just relative
// There is an issue on iOS 5 that causes the memory capacity to be set to 0 whenever a UIWebView is
// used for the first time. This will correct that issue.
NSLog(#"in fetchImageForURL %#",url);
if([[NSURLCache sharedURLCache] memoryCapacity] != URLMemoryCachSize)
{
[[NSURLCache sharedURLCache] setMemoryCapacity:URLMemoryCachSize];
}
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0f];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse)
{
NSData *data = [cachedResponse data];
NSLog(#"from cache");
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
else
{
returningResponse:&response error:&error];
// NSLog(#"loading synchronously");
[NSURLConnection sendAsynchronousRequest:request
queue:fetcherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}];
// [self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
}
in tableview controller i am calling follwing method but it crsahes for IOS 4.3 but same works for IOS 5
tableviewcontroller.m
-viewdidload()
{
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
//[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
UIImage *image = [UIImage imageWithData:data];
[self.images setObject:image forKey:index];
[table1 beginUpdates];
[table1 reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
[table1 endUpdates];
}];
}
If you look at the documentation for sendAsynchronousRequest, it requires iOS 5. If you need to support iOS 4.3, you'll have to use connectionWithRequest:delegate: or initWithRequest:delegate: and then implement the NSURLConnectionDataDelegate methods (which, while a little more work, offers other advantages such as being able to monitor the progress or cancel the request if you need).
Or, as the answer provided at that other question suggests, write your own method that provides the sendAsynchronousRequest functionality but that actually calls sendSynchronousRequest.
Or, just replace your call with sendAsynchronousRequest:
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// do something with `data`, `error`, and `response`
}];
With a call to sendSynchronousRequest that you'll perform on some NSOperationQueue queue. So, first, define a property for your operation queue:
#property (nonatomic, retain) NSOperationQueue *networkQueue;
And then initialize it, for example in viewDidLoad:
self.networkQueue = [[NSOperationQueue alloc] init];
self.networkQueue.name = #"com.domain.app.networkqueue";
self.networkQueue.maxConcurrentOperationCount = 4;
And then you can use that network operation queue to call sendSynchronousRequest:
[self.networkQueue addOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// assuming you want to interact with your UI and or synchronize changes to your model, dispatch this final processing back to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do something with `data`, `error`, and `response`
}];
}];
Bottom line, just replace your calls to sendAsynchronousRequest with methods, such as sendSynchronousRequest, that were available in iOS 4.3.
i have this method
- (BOOL)connectedToInternet
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.google.com/"]];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request
returningResponse:&response error: NULL];
return ([response statusCode] == 200) ? YES : NO;
}
that method is taking a few seconds to do it, im using it in a simple if conditional to know if i have internet connection.
is there any way to do it in a background thread without having to change all code.
I'm calling it this way
if([self connectedToInternet])
So if i do it in a background thread i cant get the return value and then my method cant return the value.
If i have to change all it doesn't worth it.
I hope u can understand my question and thanks for any help.
In Apple's "Reachability" Code Sample note the reachabilityWithAddress: method please.
You can do something similar to this using blocks;
definition (.h)
+ (void)isConnectedToInternet:(void (^)(BOOL connected))block;
implementation (.m)
+ (void)isConnectedToInternet:(void (^)(BOOL))block
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.google.com/"]];
[request setHTTPMethod:#"HEAD"];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if (block) {
block( ([httpResponse statusCode] == 200) ? YES : NO);
}
}];
}
then call it like
[MyClass isConnectedToInternet:^(BOOL connected) {
if (connected) {
// do stuff;
}
}];
I don't know what exactly what you want to do, but what you want to use is probably :
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
//your asynchronous code here
});
But by using an if condition, you need the result in order to continue, don't you? So why running the code in background?
I would suggest the method which you are implementing to know 'if Internet is connected or not' is not the most optimized one... few days back I also tried to implement the same thing.. and I came across couple of solutions, over Internet.. and I wrote about it on my blog.. Checking Internet connection in cocoa.
My preferred way to know if network is connected or not is by using Reachability class. You can get clue on using it from this code: NetworkCheckUtility.
Hope this helps :-)