Asynchronous image download - iphone

The following code is within the cellForRowAtIndexPath. I need to edit this code to the following situations;
1.) If there was a problem to download the image this block should return a setFailedBlock block, How can i add it to my code ?
2.) While the image is downloading the user changes the view, then i want to stop executing this code (Stop the download). I think i should write the cancel the block in the viewdiddissapear or viewwilldissapear methods. But i don't know how to write the code to cancel the download. Can someone show me how to do this ?
(note: this block is inside the cellForRowAtIndexPath method so have to access it from viewdiddissapear or viewwilldissapear)
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *someimageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:someimageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageviewofsomeimage setImage:[UIImage imageWithData:someimageData ] ];
});
});

if you use NSURLConnection, this class has cancel
AsyncURLConnection discussion

Related

How to update data on the main thread after running code in the background?

I have this dispatch_queue code that I'm using to make 3 different data requests. Then I'm updating the tableView on the main thread. What else can I put in the main thread? I'm using this [self requestExtendedData]; to download data from a web service that is then parsed and set to UILabels etc...
My question is: if I have the [self requestExtendedData]; running in the background thread how do I update the UILabels with the content from this data in the main thread? Should I put everything else in the main thread area? All the UIView, UILabels and UIButton objects etc...
thanks for the help
dispatch_queue_t jsonParsingQueue = dispatch_queue_create("jsonParsingQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(jsonParsingQueue, ^{
[self requestUserData]; // request user comments data
[self requestExtendedData]; // request extended listing info data
[self requestPhotoData]; // request photo data
// once this is done, if you need to you can call
// some code on a main thread (delegates, notifications, UI updates...)
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
// release the dispatch queue
dispatch_release(jsonParsingQueue);
This is what apple document saying:
Note: For the most part, UIKit classes should be used only from an application’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your application’s user interface in any way.
That means you should put all your UIView, UILabels and UIButton objects code in the main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
// Your UI update code here
});
For Swift3 :
DispatchQueue.main.async{
self.tableView.reloadData()
// any UI related code
self.label.text = "Title"
self.collectionView.reloadData()
}
Try something like this after your dispatch_async
[self performSelectorOnMainThread:#selector(doUIStuffOnMainThread:)
withObject:self.tableView.data
waitUntilDone:YES];
Try adding one main queue operation like this :
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
[your_tableview performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:YES];

Updating label on the main thread is not working

I'm trying to update a label while different tasks are proceeding. I searched and used different options and endup using this way but it still doesn't work:
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Creating your account..." waitUntilDone:NO];
DCConnector *dccon = [DCConnector new];
ContactsConnector *conCon = [ContactsConnector new];
if (![dccon existUsersData]) {
[dccon saveUsersInformation:device :usDTO];
//created account
//get friends -> Server call
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Checking for friends..." waitUntilDone:NO];
NSMutableArray *array = [conCon getAllContactsOnPhone];
// save friends
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Saving friends.." waitUntilDone:NO];
if ([dccon saveContacts:array]) {
[processStatusLable performSelectorOnMainThread:#selector(setText:) withObject:#"Friends saved successfully.." waitUntilDone:NO];
}
}
The last performSelector is getting executed (at least I see the label text changed on the view), but all other selectors are not working. Any idea why?
EDIT 1
- (void)updateLabelText:(NSString *)newText {
processStatusLable.text = newText;
}
we can use the following code to run something on the main thread,
dispatch_async(dispatch_get_main_queue(), ^{
//set text label
});
Using that we can write a method like this,
- (void)updateLabelText:(NSString *)newText {
dispatch_async(dispatch_get_main_queue(), ^{
processStatusLable.text = newText;
});
}
Finally, you can use change your code this way,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateLabelText:#"Creating your account..."];
DCConnector *dccon = [DCConnector new];
ContactsConnector *conCon = [ContactsConnector new];
if (![dccon existUsersData]) {
[dccon saveUsersInformation:device :usDTO];
//created account
//get friends -> Server call
[self updateLabelText:#"Checking for friends..."];
NSMutableArray *array = [conCon getAllContactsOnPhone];
// save friends
[self updateLabelText:#"Saving friends.."];
if ([dccon saveContacts:array]) {
[self updateLabelText:#"Friends saved successfully.."];
}
}
});
How fast do you run through this sequence of updates? If it is faster than a second, you aren't likely going to see all of 'em.
Making them wait until done is unlikely to impact anything as the drawing is done asynchronously anyway.
Note that your method names are unconventional; methods shouldn't be prefixed with get and saveUsersInformation:: is discouraged (try something like saveUsersInformationToDevice:usingDTO:).
How much time elapses between the calls to update the text field? The whole process takes a minute, but how is that time divided?
What is your main event loop doing otherwise? Running modally or running normally?

Having Trouble With GCD and Loading Thumbnails in TableView

I have the following code that attempts to load a row of thumbnails in a tableview asynchronously:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
__block GraphicView *graphicView;
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
graphicView = [[tableViewCell.contentView.subviews objectAtIndex:i] retain];
graphic = [[self.thumbnailCache objectForKey: [NSNumber numberWithInt:startingThumbnailIndex + i]] retain];
if (!graphic)
{
graphic = [[self graphicWithType:startingThumbnailIndex + i] retain];
[self.thumbnailCache setObject: graphic forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
}
[graphicView setGraphic:graphic maximumDimension:self.cellDimension];
});
[graphicView setNeedsDisplay];
dispatch_async(dispatch_get_main_queue(),
^{
CGRect graphicViewFrame = graphicView.frame;
graphicViewFrame.origin.x = ((self.cellDimension - graphicViewFrame.size.width) / 2) + (i * self.cellDimension);
graphicViewFrame.origin.y = (self.cellDimension - graphicViewFrame.size.height) / 2;
graphicView.frame = graphicViewFrame;
});
[graphicView release];
[graphic release];
});
}
However when I run the code I get a bad access at this line: [graphicView setNeedsDisplay]; It's worth mentioning that the code works fine when I have it set up like this:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_async(dispatch_get_main_queue(),
^{
//put all the code here
});
}
It works fine and the UITableView loads asynchronously when it's first called, however the scrolling is still really choppy.
So I'd like to get the to get the first bit of code to work so I can get the drawing done in the global thread instead of the main thread (which I assume will fix the choppy scrolling?).
Since iOS4 drawing is able to be done asynchronously so I don't believe that is the problem. Possibly I'm misusing the __Block type?
Anyone know how I can get this to work?
You completely misunderstand how to use GCD. Looking at your code:
__block GraphicView *graphicView;
Your variable here is not initialised to nil. It is unsafe to send messages to.
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
//statements
});
Your dispatch statement here returns immediately. The system works for you to spin this task off on a different thread. Before, or perhaps at the same time as, the above statements are executed we move on to the next line of execution here...
[graphicView setNeedsDisplay];
At this point graphic view may or may not have been initialised by your dispatch statement above. Most likely not as there wont have been time. As it still hasn't been initialised it points to random memory and hence trying to send messages to it causes EXC_BAD_ACCESS.
If you want to draw cell contents asynchronously (or pre-render images or whatever.) I thouroughly reccommend watching WWDC 2012 session 211 "Building Concurrent User Interfaces on iOS". They do almost exactly what you seem to be attempting to do and explain all the pitfalls you can run into.
I think the issue is because you are trying to re-draw the UIView on a working thread. You should move this:
[graphicView setNeedsDisplay];
To the main queue.

GCD, Threads, Program Flow and UI Updating

I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// I've tried to use GCD but similar issue and can't get UI updated.
//dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
//dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});
}
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
if (numSolutions == 0) {
solveButton.title = #"Not Solvable";
} else {
solveButton.title = #"Solve Puzzle";
}
}
Needs to run in background so UI can be updated:
-(void)createTreeFromNode:(TreeNode *)node
{
// Tried using GCD
dispatch_queue_t main_queue = dispatch_get_main_queue();
...Create Tree Node and find Children Code...
if (!solutionFound){
// Solution not found yet so check other children by recursion.
[self createTreeFromNode:newChild];
} else {
// Solution found.
numSolutions ++;
if (maxMoves < newChild.numberOfMoves) {
maxMoves = newChild.numberOfMoves;
}
if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
solutionNode = newChild;
minMoves = newChild.numberOfMoves;
// Update UI on main Thread
dispatch_async(main_queue, ^{
minMovesLabel.stringValue = [NSString stringWithFormat:#"%d",minMoves];
numSolutionsLabel.stringValue = [NSString stringWithFormat:#"%d",numSolutions];
maxMovesLabel.stringValue = [NSString stringWithFormat:#"%d",maxMoves];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added NO.
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// NO - do not wait or enable here.
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);
- (void)process {
dispatch_async(backgroundQueue, ^(void){
//background task
[self processHtml];
dispatch_async(main, ^{
// UI updates in main queue
[self workDone];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
__weak MyClass *weakSelf = self; //must be weak to avoid retain cycle
//Assign async work
dispatch_async(queue,
^{
[weakSelf doWork];
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf workDone];
});
});
queue = nil; //Using ARC, we nil out. Block always retains the queue.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.

Updating UI after completion of background thread

I'm implementing a simple twitter client for the iPhone using a UITableView. I fetch the picture of each twitter user in my feed when their cell appears in tableView: cellForRowAtIndexPath:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *profileImage = [tweet.user getProfileImageDataInContext:self.fetchedResultsController.managedObjectContext];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = profileImage;
});
});
Here is the code to fetch the image:
if (!self.profileImage)
{
sleep(2);
self.profileImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.profileImageURL]];
//// if we recently scheduled an autosave, cancel it
[TwitterUser cancelPreviousPerformRequestsWithTarget:self selector:#selector(autosave:) object:context];
// request a new autosave in a few tenths of a second
[TwitterUser performSelector:#selector(autosave:) withObject:context afterDelay:0.2];
}
return [UIImage imageWithData:self.profileImage];
Here is the error I'm getting:
twitterClient[10743:15803] bool _WebTryThreadLock(bool), 0x59bac90: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
I think it's also worth mentioning that this happens when I scroll through the tableview very quickly when it hasn't been populated yet.
I would like the main UI to update upon completion of the download. The actual twitter app for iPhone does this quite well.
Any suggestions?
What’s the crash? A pretty standard pattern for things like this is
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do background stuff
dispatch_async(dispatch_get_main_queue(), ^{
// do main-queue stuff, like updating the UI
});
});
You're using GCD fine, your crash is happening because you're calling dataWithContentsOfURL in a background thread, which is not thread safe.
See: Does -dataWithContentsOfURL: of NSData work in a background thread?