Asynchronous url requests inside dispatch_async - ios

I am trying to implement asynchronous url requests in a particular function, I want all these requests to complete and then do a particular action but the action precedes the requests i.e, it is getting called before the requests complete.
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_async(fetchQ, ^{
[self myAsyncMultipleURLRequestFunction];
dispatch_sync(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
});
-(void)myAsyncMultipleURLRequestFunction
{
for (int i=0; i<count; i++)
{
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
}
}
now updateUIFunction gets called before myAsyncMultipleURLRequestFunction completes all requests. Also tried this with NSOperaitonQueue but could not do what I really want.
[_operationQ addOperationWithBlock: ^ {
for (int i=0; i<count; i++)
{
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
// updating UI
[self updateUIFunction];
}];
}];
I know this is simple but I am running outta time, any help is appreciated.

#tkanzakic is on the right path. The correct construct to use is the dispatch_group_t. But the implementation could be improved. By using a semaphore you can launch all your downloads asynchronously and still make sure that you don't have too many running concurrently. Here is a code sample that illustrates how you should use dispatch_group_t as well as make all your downloads parallel:
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_group_t fetchGroup = dispatch_group_create();
// This will allow up to 8 parallel downloads.
dispatch_semaphore_t downloadSema = dispatch_semaphore_create(8);
// We start ALL our downloads in parallel throttled by the above semaphore.
for (int i=0; i<count; i++) {
dispatch_group_async(fetchGroup, fetchQ, ^(void) {
dispatch_semaphore_wait(downloadSema, DISPATCH_TIME_FOREVER);
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:requestArray[i] delegate:self];
dispatch_semaphore_signal(downloadSema);
});
}
// Now we wait until ALL our dispatch_group_async are finished.
dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
// Update your UI
dispatch_sync(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
// Release resources
dispatch_release(fetchGroup);
dispatch_release(downloadSema);
dispatch_release(fetchQ);

You can create a dispatch_group_t and then use dispatch_group_notify to execute the updateUIFunction when the previous block of the group finish running, for example:
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_async(fetchQ, ^{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self myAsyncMultipleURLRequestFunction];
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
});
});

First Configure run loop.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[NSURLConnection connectionWithRequest:request delegate:self];
while(!self.finished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
});
Try this

Related

Convert GCD to NSOperationqueue

The task can be cancelled if while using NSOperation whereas if I use GCD, then once I assign the task to queue then we are not able to cancel it, therefore I wonder how could I convert my following implementation in GCD to NSOperation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
[self addAllImages];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self pageControlSetUp];
self.fullScreenImageView.hidden = YES;
});
})
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[self addAllImages];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self pageControlSetUp];
self.fullScreenImageView.hidden = YES;
});
}];
[queue addOperation:operation];
//cancel operation
[operation cancel];
//or to cancell all operations
[queue cancelAllOperations];

How to get API Data Asynchronously in Objective-C?

All my work is going fine, but There is a little problem in it. I have my NSURLRequest in -(void)viewDidLoad{} and it took some time to fetch data from server. I want it to be done in asynchronous way.
Following is my code please suggest me what should I implement.?
Thanks in advance to all of you. :)
- (void)viewDidLoad {
[super viewDidLoad];
[[self tableView2]setDelegate:self ];
[[self tableView2]setDataSource:self];
array=[[NSMutableArray alloc]init];
NSString *castString = [NSString stringWithFormat:#"https://api.themoviedb.org/3/movie/%#/credits?api_key=c4bd81709e87b12e6c74a08609433c49",movieIDinString];
NSURL *url=[NSURL URLWithString:castString];
NSURLRequest *request=[NSURLRequest requestWithURL:url];
connection=[NSURLConnection connectionWithRequest:request delegate:self];
if (connection)
{
webData= [[NSMutableData alloc]init];
}
try this..
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
NSString *castString = [NSString stringWithFormat:#"https://api.themoviedb.org/3/movie/%#/credits?api_key=c4bd81709e87b12e6c74a08609433c49",movieIDinString];
NSURL *url=[NSURL URLWithString:castString];
NSURLRequest *request=[NSURLRequest requestWithURL:url];
connection=[NSURLConnection connectionWithRequest:request delegate:self];
if (connection)
{
webData= [[NSMutableData alloc]init];
}
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
// reload table view here
[_activityIndicatorImageView stopAnimating];
});
});
If you are using API, it will take some time, to fetch data from server. At this time, you have to use background thread and show activity indicator in main thread. After getting data from API, you need to change thread to main thread. Please check my below code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// background thread
// code for API call
dispatch_async(dispatch_get_main_queue(), ^{
// main thread
});
});
You can use callback method also.
[helperApi instaUserDetails:finderDetailsDataDict andCallback:^(id jsonResponse) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([[jsonResponse objectForKey:#"code"] intValue] == 200) {
userDetailsDict = [jsonResponse objectForKey:#"data"];
mediaArray = [[[[jsonResponse objectForKey:#"data"] objectForKey:#"user"] objectForKey:#"media"] objectForKey:#"nodes"];
}
[activityIndicator stopAnimating];
[self createUI];
});
}];
NSURLConnection is deprecated now. Try to use NSURLSession.
Try AFNeworking. It provides several options for async downloads/uploads, with completions blocks.

Progress View and background loading

my code is loading 7 pics from url and adding their data to an array. in the end of the process I do get an 8 objects array, but I'm trying to show a progress bar until the process of loading all the photos finished.
I do not have an idea how to do that...
here is the code
-(void)SetUpDrinks
{
loadingView.hidden=NO;
[loadingView_activity startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible=YES;
imgsDATA = [[NSMutableArray alloc] init];
for (int i=0; i<8; i++) {
imageDownloadNum++;
absPath = [NSString stringWithFormat:#"http://domain/app/menu/drinks/%i.png",imageDownloadNum];
trimmedAbsPath = [absPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL *imgURL = [NSURL URLWithString:trimmedAbsPath];
NSLog(#"%#",imgURL);
imgDATA = [[NSData alloc] initWithContentsOfURL:imgURL];
[imgsDATA addObject:imgDATA];
}
dispatch_async(dispatch_get_main_queue(), ^{
loadingView.hidden=YES;
[loadingView_activity stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
[self RefreshImg];
});
});
}
You could make an NSOperation subclass to download your image and then use a dispatch_group. Dispatch groups are a way to block a thread until one or more tasks finish executing - in this scenario we are waiting for all of your downloads to finish.
You can now use the progressBlock to update the UI and let the user know how many of the images have finished downloading.
If you want progress for an individual download then take a look at the NSURLConnection reference. In particular - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)enqueueGroupOfOperations:(NSArray *)operations
progress:(void (^)(NSUInteger completedCount, NSUInteger totalOperations))progressBlock
completion:(void (^)(NSArray *operations))completionBlock;
{
NSParameterAssert(operations);
NSParameterAssert(progress);
NSParameterAssert(completion);
__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *dependentOperation = [NSBlockOperation blockOperationWithBlock:^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completion(operations);
});
dispatch_release(group);
}];
for (NSOperation *operation in operations) {
operation.completionBlock = ^{
dispatch_group_async(group, dispatch_get_main_queue(), ^{
NSUInteger count = [[operations indexesOfObjectsPassingTest:^BOOL(NSOperation *operation, NSUInteger idx, BOOL *stop) {
return [operation isFinished];
}] count];
progress(count, [operations count]);
dispatch_group_leave(group);
});
};
dispatch_group_enter(group);
[dependentOperation addDependency:operation];
}
[self.operationQueue addOperations:operations waitUntilFinished:NO];
[self.operationQueue addOperation:dependentOperation];
}
If this is too much for you then you can go over to AFNetworking where this is all done for you, https://github.com/AFNetworking/AFNetworking. But its always nice to know how some of this stuff works.

AFNetworking and NSURLConnection on main thread

EDIT: The highlighted row in the screenshot is what I have a problem with, why is NSURLConnection running on [NSThread main] when I'm not calling it, AFNetworking is.
I'm using AFNetworking for my project, but when running Time Profiler in Instruments I'm seeing a lot of activity on the main thread for NSURLConnection, I have a feeling this is not what I want.
My method is
- (void)parseArticles {
NSMutableArray *itemsToParse = [[FMDBDataAccess sharedDatabase] getItemsToParse];
NSMutableArray *operations = [[NSMutableArray alloc] init];
for (Post *p in itemsToParse) {
NSMutableString *strURL = [NSMutableString new];
[strURL appendString:#"http://xxxxxxxxxxxxxxxxxxxxxx.php?url="];
[strURL appendString:[p href]];
NSURL *url = [NSURL URLWithString:strURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[[ParserClient sharedInstance] registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(loginParseQueue, ^{
Parser *parse = [[Parser alloc] init];
[parse parseLink:responseObject rowID:[p rowID]];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",error);
}];
[operations addObject:operation];
}
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:3];
[operationQueue addOperations:operations waitUntilFinished:NO];
}
Why would AFNetworking be using the main thread? and how do I fix it.
AFNetworking is running on a child thread not in main thread, but every thread has a main method, which is on the image you post. This is not the main thread.Now tell me What do you want to fix?
It's because AFNetworking uses "successCallbackQueue" to route the completion block :
AFHTTPRequestOperation.m :
self.completionBlock = ^{
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, self.responseData);
});
}
}
};
You can simply assign a different thread to success and failure completion blocks :
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.successCallbackQueue = backgroundQueue;
operation.failureCallbackQueue = backgroundQueue;
EDIT:
Here is some code to run operations in a background thread. Use of any function called from the UI thread will run on on the UI thread. You can use a technique similar to the one specified below to run your operation on a background thread, and then dispatch the result back to the UI thread for later use.
Here is the technique I used, you may replace my sendSynchronousRequest call with your AFHTTPRequestOperation :
Specify a special type (a block) so you can pass blocks of code around.
typedef void (^NetworkingBlock)(NSString* stringOut);
Then, you need to dispatch to a background thread, so as not to freeze your UI thread.
Here's a function to call stuff in a background thread, and then wait for a response, and then call a block when done without using the UI thread to do it:
- (void) sendString:(NSString*)stringIn url:(NSString*)url method:(NSString*)method completion:(NetworkingBlock)completion {
//build up a request.
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
NSData *postData = [stringIn dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPMethod:method];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"]; //or whatever
[request setHTTPBody:postData];
//dispatch a block to a background thread using GCD (grand central dispatch)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response;
NSError* error;
//request is sent synchronously here, but doesn't block UI thread because it is dispatched to another thread.
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//call complete, so now handle the completion block.
if (completion) {
//dispatch back to the UI thread again
dispatch_async(dispatch_get_main_queue(), ^{
if (responseData == nil) {
//no response data, so pass nil to the stringOut so you know there was an error.
completion(nil);
} else {
//response received, get the content.
NSString *content = [[NSString alloc] initWithBytes:[responseData bytes] length:responseData.length encoding:NSUTF8StringEncoding];
NSLog(#"String received: %#", content);
//call your completion handler with the result of your call.
completion(content);
}
});
}
});
}
Use it like this:
- (void) myFunction {
[self sendString:#"some text in the body" url:#"http://website.com" method:#"POST" completion:^(NSString *stringOut) {
//stringOut is the text i got back
}];
}

How to manage AFNetworking request number?

In my iOS App,I have hundreds of image download.
I use AFNetworking to get those images.I want to manage the request number of AFWorking.
This is my code:
The problem is:It will block my UI.
THX for help me!
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (NSString *urlString in self.downloadImageList) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSString *filename = url.lastPathComponent;
NSURL *outputFileURL = APPLICATION_DOCUMENTS_DIRECTORY;
outputFileURL = [outputFileURL URLByAppendingPathComponent:[NSString stringWithFormat:#"%#/images/%#",self.boardId,filename]];
dispatch_group_async(group, dispatch_get_main_queue(), ^{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamWithURL:outputFileURL append:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.downloadImageList removeObject:urlString];
NIDINFO(#"download success %#",filename);
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NIDERROR(#"download image error:%#\n%#",error,urlString);
dispatch_semaphore_signal(semaphore);
}];
[operation start];
});
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
dispatch_release(group);
The problem is: It will block my UI.
Well yeah, that's sort of the point of dispatch_semaphore_t. If you remove those calls, your AFNetworking calls will execute asynchronously in the background without affecting your main / UI thread.
I don't know the full context of where this code is being used, but it looks like you may want to consider having the method that calls this take a block parameter, that can be called once all of the requests have finished. You may also want to take a look at AFHTTPClient's batch operations features, which allows you to track the progress of a group of operations, getting callbacks for each individually, and when all of then finish.

Resources