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.
Related
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.
I am trying to load images by their URL and store them in NSMutableArray in order. My current code works properly if I were not to care about storing the images in order, however it stores them not in order. It currently stores the images in the articleImage array based on the speed at which the asynchronous requests are completed. I have tried playing around with insertObject:AtIndex but could not get anything to work. To clarify, the NSMutableArray that I am trying to store the images in (in orderly fashion) is articleImage.
Here is some code from my viewDidLoad:
dispatch_async(dispatch_get_main_queue(), ^{
if(articleInfoJSONArray.count > 0)
{
for(int i=0; i<articleInfoJSONArray.count; i++)
{
[issueID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"issueID"]];
[articleID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleID"]];
NSString *imageLink = [[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleImage"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageLink]];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[articleImage addObject:image];
if(articleImage.count == articleInfoJSONArray.count)
[self imagesLoaded];
});
});
}
}
});
Here is my imagesLoaded:
- (void)imagesLoaded
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
ViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"MainView"];
[self presentViewController:vc animated:NO completion:nil];
}
Try to use dispatch_group. A dispatch group monitors work that has been added to it, and it will know when that work is done. :) http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
One way i did an image download is with NSOperationQueue and NSOperation. You could define a NSOperationQueue in your header file:
#property (strong, nonatomic) NSOperationQueue *sequentialOperationQueue;
in your implementation do:
self.sequentialOperationQueue = [[NSOperationQueue alloc] init];
self.sequentialOperationQueue.maxConcurrentOperationCount = 1;
then you can add:
for (NSDictionary *imageDict in imagesToFetch) {
ImageDownloadOperation *imgDownloadOperation = [[ImageDownloadOperation alloc] initWithImageLocationDict:imageDict];
[self.sequentialOperationQueue addOperation:imgDownloadOperation];
}
LogoDownloadOperation is a subclass of NSOperation. this way you always have only one active download and process them in the order you want. For details on NSOperation check the apple doc.
in extract i did in ImageDownloadOperation:
- (void)start {
NSURL *imageUrl = [NSURL URLWithString:self.imageDict[#"imageUrl"]];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDownloadTask *downloadPhotoTask = [session
downloadTaskWithURL:imageUrl
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
self.sessionTask = nil;
[self done];
return;
}
NSData *imageData = [NSData dataWithContentsOfURL:location];
NSBlockOperation *saveImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
[SharedAppDelegate.entityManager saveImage:imageData
imageDict:self.imageDict
inManagedObjectContext:SharedAppDelegate.managedObjectContext];
}];
saveImageBlockOperation.qualityOfService = NSQualityOfServiceBackground;
[[NSOperationQueue mainQueue] addOperation:saveImageBlockOperation];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.sessionTask = nil;
[self done];
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
As you can see, i store the imageData via my AppDelegate in CoreData. Instead of my way, you could give the ImageDownloadOperation a pointer to your NSMutableArray, then you can store the data direct in your array.
You could make an array of [UIImage new] then once the task is complete
replace the empty image images[i] = newImage
EDIT
NSMutableArray *imageArray = [NSMutableArray new];
for (int i=0; i<articleInfoJSONArray.count; i++) {
[imageArray addObject:[UIImage new]];
}
for (int i=0; i<articleInfoJSONArray.count; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
//download image
imageArray[i] = downloadedImage;
});
}
I am running around the issue, that MBProgressHUD should be updating its View with a Checkmark/X for succeeded/failed requests. Somehow this doesnt really work as intended and the update only works after all the code has executed.
initializing the HUD
...
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Verifying Credit Card";
_HUD = hud;
CWAPIClient *client = [CWAPIClient sharedClient];
client.delegate = self;
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.clubw.billing", 0);
dispatch_async(backgroundQueue, ^{
[client save:billingProfile with:[Address defaultBillingAddress]];
});
....
Callback to process information:
self POST:[NSString stringWithFormat:#"user/%#/billingprofile", [[Profile defaultProfile] userId]] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate CWAPIClient:self doneVerifyingCreditCard:responseObject];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
callback when action is complete
BOOL success = [[jsonResponse objectForKey:#"success"] boolValue];
if (success)
{
NSLog(#"Thread: %#", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *checkmarkImage = [UIImage imageNamed:#"checkmark.png"];
UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmarkImage];
_HUD.customView = checkmarkView;
_HUD.labelText = #"Credit Card Verified!";
_HUD.mode = MBProgressHUDModeCustomView;
NSLog(#"Thread: %#", [NSThread currentThread]);
sleep(5);
[_HUD hide:YES];
});
After the 5 seconds, it simply closed the HUD - and the updated one flashes for a second.
It seems, like this is a threading issue - but I cant seem to figure out where it is throwing up.
In the portion of the code where you make your callback, be sure to perform the callback on the main queue as well.
I building an app has 4 tab (Tabbar Controller), and each tab I call a function (updateArray) after 2s. I want to when click on other tab, updateArray() function is kill. My problem is when on tab, updateArray() call after 2s, when I click on other tab, this function is still call.
This is updateArray()
-(void)updateArray{
while (loop)
{
[NSThread sleepForTimeInterval:2.0];
[FileCompletedArray removeAllObjects];
[temp removeAllObjects];
[UserNameArray removeAllObjects];
NSURL *url1 = [NSURL URLWithString:#"server"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL: url1] ;
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success");
NSString * parsexmlinput = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"Response in Loop CompleteView: %#", parsexmlinput); //000400010001
// dispatch_async(dispatch_get_main_queue(), ^{
[self parseXMLFile:parsexmlinput];
NSLog(#"File Completed array: %#", FileCompletedArray);
NSLog(#"File Temp out array: %#", temp);
NSLog(#"File Completed count: %lu",(unsigned long)[ FileCompletedArray count]);
NSLog(#"File Temp out count: %lu", (unsigned long)[temp count]);
if([FileCompletedArray count] != [temp count])
{
temp = [FileCompletedArray mutableCopy];
NSLog(#"File Temp 1 array: %#", temp);
[_tableView reloadData];
NSLog(#"File Temp 2 array: %#", temp);
}
[alert dismissWithClickedButtonIndex:0 animated:YES];
//});
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
}
And in viewwillappear()
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
temp = [FileCompletedArray mutableCopy];
[self performSelectorInBackground:#selector(updateArray) withObject:nil ];
}
In my function, i used [NSThread sleepForTimeInterval:2.0];, I don't know how to kill it. Do you have suggestions ? Thanks in advance
You shouldn't really use sleepForTimeInterval, you should use performSelector:withObject:afterDelay: (and cancelPerformSelectorsWithTarget:) or dispatch_after.
As it is, you can add a BOOL attribute that is used to decide if the thread should continue after the sleep or whether it should exit (return).
To control any thread you have to use NSOperation Using this you can control any running thread.
Create a BOOL when you click on another tab set it to FALSE. Use this with dispatch after.
#property (nonatomic, assign) BOOL doUpdate;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if(doUpdate){
//update work.
}
});
You should take look at using NSOperation and then you could call [NSOperation cancelAllOperations] when clicking on a another tab.
Good luck,
Booranger
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