stop indicator in asynchronous downloading - iphone

I am adding all image downloading operation into Nsoperationqueue. But some times indicator will display for forever. Here I have paste some of my code. Is there any way to stop indicator. Here I am checking "operationcount". If it will become 1 means its last operation so I am stoping indicator but sometimes its not working any help will be appreciated.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//filename = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:self.iconImage];
myobject.iconImage = [[UIImage alloc] initWithContentsOfFile:self.filename];
myobject.isImageLoaded = YES;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
NSLog(#"finsh");
AboutUSAppDelegate *appdel=(AboutUSAppDelegate*)[UIApplication sharedApplication].delegate;
NSLog(#"%d",[appdel.queue operationCount]);
if ([appdel.queue operationCount]==1) {
//code to stop indicator
//Using main thread
}
Here I have added Nslog for operation count but sometimes it prints last value 2 two times. other values only 1 times.

You need to ensure that all UI calls are made on the main thread of operation. So showing the activity indicator, making it spin, stopping it and hiding it all need to be done on the main thread.
A reasonable way to achieve this is to expose methods in your view controllers to do it and from inside your Operation code call them on the main :-
Look up
performSelectorOnMainThread:<(SEL)aSelector> withObject:<(id)arg> waitUntilDone:<(BOOL)wait>

Simple answer, you can not check the operation queue in the delegate method implementation. This method get called when only 1 of the operation queue get done, it will not be called by any other methods in your queue.
Check the queue where you initialized them.

Related

How to Stop a currently executing thread

Scenario is like this--
In my app there is an Scroll View with many instances of
MyCustomImageDownloaderController(containing imageViews where images are to be assigned after downloading) .
As per our requirement, an image has to be downloaded as we move on to a page.
Scroll View + (MyCustomImageDownloaderController1, MyCustomImageDownloaderController2, MyCustomImageDownloaderController3.... etc)
Let's say i am scrolling on it,
i reached to page 1 --> image for it should start downloading
i reached to page 2 --> image for it should start downloading...so on
and if i am on page 3 and images for previous pages if not been dowloaded, they should stop downloading.
So i tried it with using threads..
on API..
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender{
Step 1) calculated currentPageNumber
Step 2) started thread for downloading image with url for this currentPage
//startBackGroundThreadForPlaceImage:(NSURL *) url
Step 3)stopped thread for previous page , if that is still running
}
Now My MyCustomImageDownloaderController is as
-(void) startBackGroundThreadForPlaceImage:(NSURL *) url{
if(isImageDownloaded == NO){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//[self performSelectorInBackground:#selector(loadImageInBackground:) withObject:imageUrl];
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(loadImageInBackground:) object:imageUrl];
//[NSThread detachNewThreadSelector:#selector(loadImageInBackground:) toTarget:self withObject:imageUrl];
[myThread start];
NSLog(#"The current thread is %# ", [[NSThread currentThread] name]);
[pool release];
}
}
NOW Here selector does the work of loading image and assigning to image view
Now Stopping the thread
-(void) stopBackgroundThread{
[myThread cancel];
//[[NSThread currentThread] cancel];
//if([[NSThread currentThread] isCancelled]) {
//[NSThread exit];
//}
[NSThread exit];
}
-(BOOL) isThreadRunning{
return [myThread isExecuting];
}
So i tried a lot of things, but could not Stop the thread in between..
Basically once instantiated thread using any of three methods
1) perform Selector in BackGround
2) NSThread detach new thread
3) NSThread alloc..init with..
In first 2 methods how to get the instance of the newly created thread, so that i could stoop it,
as NSThread currentThread doest not give that
in Method 3,
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(loadImageInBackground:) object:imageUrl];
when i tried
[myThread cancel];
It did not cancel that thread,,
When i tried
[NSThread exit];
it hangs on current screen,,,,i guess it has stopped the main thread
Please help me
Thanks in Advance*strong text*
It's generally better to ask the thread to stop, rather than forcing it, which should be considered a last resort. You should, therefore, frequently check a 'stop flag' and when this gets set, terminate the current thread (by simply exiting the method, in this case). You just need to provide a property on the class the thread is operating on so callers can ask the thread to stop.
It's no different in C++ or Java.

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.

my progressView is not refreshing

When my app starts for the first time, I have a background process which runs off of a thread and the user should see a progressView which should show the progress being made. The code below shows how I am setting up my thread and progressView
//from viewDidLoad
progView.hidden = NO;
progView.progress = 0.0;
[NSThread detachNewThreadSelector:#selector(buildTable) toTarget:self withObject:nil];
-(void)buildTable
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(addData) withObject:nil waitUntilDone:NO];
[pool release];
}
the function addData is where I update the progressView using the following code -
progView.progress = 0.1
However, the progressView is visible but even though the background process is updating the progressView using the above code it is not appearing so on the screen. Do I need to use some form of StartAnimating while the background process is running?
You should only update user interface items from the main thread. You're probably not seeing any change to the progress indicator because you're trying to change it from a background thread.
A very simple strategy to do this is to call performSelectorOnMainThread from your background thread to call a simple method that updates the progress bar on the main thread.
For example, in your addData method (in the background thread) you can call:
[self performSelectorOnMainThread:#selector(updateProgressBar:) withObject:[NSNumber numberWithFloat:newProgressValue] waitUntilDone:false];
And then in your updateProgressBar method (which will run on the main thread), do the progress bar update using the given data:
- (void)updateProgressBar:(NSNumber *)progressValue {
progView.progress = [progressValue floatValue];
}

How to get/set a global variable (BOOL) from within a background-thread? (Xcode)

From my main thread, I launch an image loader method method-A (below). The problem is, if method-A is not finished at the time a new method-A call is made, image loading starts from the beginning.
What I want to do is, nullify any new method-A calls that are made while a previous method-A call is still doing work... The way I (attempt to) do it now is having a simple global BOOL variable (BOOL imageLoaderBusy) and using it to keep track if the method-A is still working or not (as shown below).
The problem is, the variable seems to be ignored sometimes, and new method-A calls are undesirably started...I dunno. Maybe there is a special way you need to create global variables to make them accessible / valid across multiple threads?
Can somebody please tell me what I am doing wrong? Thanks.
//Method-A called like this:
[self performSelectorInBackground:#selector(loadPagesWithGraphics:) withObject:nil];
//Method-A
-(IBAction)loadPagesWithGraphics:(id)sender{
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
if(!imageLoaderBusy){
imageLoaderBusy = YES;
// Load Images
}
imageLoaderBusy = NO;
[arPool release];
}
Thanks in advance.
Regardless of a variable being an instance variable or a global variable, if multiple threads may write to that variable concurrently, you need to lock that section of code. For instance,
-(IBAction)loadPagesWithGraphics:(id)sender{
#synchronized(self) {
if (imageLoaderBusy) return;
imageLoaderBusy = YES;
}
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
imageLoaderBusy = NO;
[arPool release];
}
Let’s say two executions of that method happen simultaneously in threads A and B, and A gets the lock first, so thread B waits for the lock to be released. From A’s perspective, imageLoaderBusy == NO so it doesn’t return, sets imageLoaderBusy = YES, and releases the lock.
Since the lock has been released, thread B can start executing. It checks imageLoaderBusy and, since thread A has set it to YES, the method returns immediately in thread B.
Thread A proceeds to load the images and sets imageLoaderBusy to NO.
Note that this means that if the method is called again in some thread it will be executed and load the images again. I’m not sure if that’s your intended behaviour; if it’s not, you’ll need another check to determine if images have already been loaded. For instance,
-(IBAction)loadPagesWithGraphics:(id)sender{
if (imagesHaveBeenLoaded) return;
#synchronized(self) {
if (imageLoaderBusy) return;
imageLoaderBusy = YES;
}
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
[arPool release];
imageLoaderBusy = NO; // not strictly necessary
imagesHaveBeenLoaded = YES;
}
You don’t need to have all the method inside a #synchronize block. In fact, critical sections should usually be kept small, especially if the lock is being applied to the whole object (self). If the entire method were a critical section, thread B would have to wait until all images are loaded before noticing that another thread was already busy/had already loaded the images.
Try to change this way:
-(IBAction)loadPagesWithGraphics:(id)sender{
if( imagesDidLoad ) return;
#synchronized(self) {
NSAutoreleasePool *arPool = [[NSAutoreleasePool alloc] init];
// Load Images
[arPool release];
//set global ivar
imagesDidLoad = YES;
}
}
and in Method-A
add
-(void) methodA {
if( !imagesDidLoad )
[self performSelectorInBackground:#selector(loadPagesWithGraphics:) withObject:nil];
}
in Method-a call a setter on you're main thread to set that BOOL.
The method to do that is : - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

NSOperations or NSThread for bursts of smaller tasks that continuously cancel each other?

I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.
The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.
The web service call and the subsequent parsing of the result I would like to move to a different thread.
I have written a simple SearchWebService class, it has only one public method:
- (void) searchFor:(NSString*) str;
This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:
- (NSArray*) resultsReady;
I can't figure out how to get this functionality 'threaded'.
If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one.
Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:
- (void) keepRunning {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SearchWebService *searchObj = [[SearchWebService alloc] init];
[[NSRunLoop currentRunLoop] run]; //keeps it alive
[searchObj release];
[pool release];
}
But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?
Hope someone could point me in the right direction, threading is giving me grief:)
Thank you.
Ok, I spend the last 8 hours reading up on every example out there.
I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.
It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.
Is called after each keystroke:
- (void) searchFieldChanged:(UITextField*) textField {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *searchString = textField.text;
if ([searchString length] > 0) {
[self performSelector:#selector(doSearch:) withObject:textField.text afterDelay:0.8f];
}
}
This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart.
(I would have that a lot lower if it where not for the small touch keyboard).
If it is allowed to time out, it is time to search.
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[queue addOperation:searchOperation];
[searchOperation release];
}
Cancel all operations that is currently in the queue. This is called every time a new search is
started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.
The implementation for the ISSearchOperation is really simple:
#implementation ISSearchOperation
- (void) dealloc {
[searchTerm release];
[JSONresult release];
[parsedResult release];
[super dealloc];
}
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
if ([self isCancelled]) return;
[self setJSONresult:/*do webservice call synchronously*/];
if ([self isCancelled]) return;
[self setParsedResult:/*parse JSON result*/];
if ([self isCancelled]) return;
[self performSelectorOnMainThread:#selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}
#end
There are two major steps, the downloading of the data from the web service and the parsing.
After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.
I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.
But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop.
NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.