Why is my UIProgressBar not being updated? - objective-c

Here is how I am attempting to update my UIProgressView in my iOS app.
[self.progressView setProgress:3.0/10.0 animated:YES];
// check the value.
NSLog(#"Progress: %0.1f", self.progressView.progress);
Why is NSLog outputting 0.0? Shouldn't it output 0.3? The progress bar isn't changing either.

First of all, make sure you set the progress value on the main thread. (and that self.progressView does in fact refer to your UIProgressView)
If that doesn't help, you may want to explicitly give the runloop some time to catch up on UI changes, e.g. using:
NSDate* futureDate = [NSDate dateWithTimeInterval:0.001 sinceDate:[NSDate date]];
[[NSRunLoop currentRunLoop] runUntilDate:futureDate];
However, you should be very careful using hacks like this, see:
Is calling -[NSRunLoop runUntilDate:] a good idea?
Using [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] to let the scheduled selectors fire
NSRunloops and forcing event processing

Related

How to wake up thread after [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture]?

I have a Download object which handles NSURLConnection.
Then I have NSOperation object (DownloadOperation) which holds Download object as property.
Download object has ability to start/pause/resume/cancel.
This is the main method of DownloadOperation
- (void)main
{
#autoreleasepool {
BOOL isDone = NO;
if (![self isCancelled]) {
[_download start]; //Download object start (creates NSURLConnection internally)
}
NSDate *distantFuture = [NSDate distantFuture];
while(!isDone && ![self isCancelled]) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture];
if (!_download.isActive) { //internal state of Download
isDone = YES;
}
}
[self completeOperation]; //this sets finished and executing flags
}
}
From outside (from UI), I manipulate with Download object: Start, Pause, Resume, Cancel.
And internally I am changing its state so when Download is finished or canceled, isActive is set to NO and while loop should end.
This works if I start Download and let it finish (in background, NSURLConnection finished and called delegate -connectionDidFinish...)
If I pause/resume Download, download will continue to download and finish (and change its internal state: isActive -> NO).
On pause I cancel NSURLConnection and on resume I create new.
Or if I cancel the download, it will also be inactive (NSURLConnection is canceled).
But here is the problem:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:distantFuture]; never returns in those cases (when I cancel NSURLConnection) and my "if" statement is never handled so this DownloadOperation is always considered running and will never exit my NSOperationQueue.
It looks like there is no event that could be fired that would cause that runloop to wake up.
I tried
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
and this kind of works but not so smooth and I don't think it is the best solution.
What I really want to know is, how to force that NSRunLoop to wake up (how to fire some event that will cause it to wake up) and continue my while loop?
Here's what I did.
Good thing is that I have notifications in my app and I know exactly when downloads are changing. So I've made an instance variable NSThread currentThread and at the beginning of main I call currentThread = [NSThread currentThread].
After I receive notification which I know should cause my thread to wake up I call:
[self performSelector:#selector(wakeUpThread:) onThread:currentThread withObject:nil waitUntilDone:NO];
- (void)wakeUpThread does nothing, it is empty method, but purpose of this is to wake up thread and cause my run loop to continue.

Concurrency and NSURLConnection

Recently I've been researching and working with NSURLConnection and concurrency.
There seem to be several different approaches, and the ones I've tried (dispatch queues and operation queues) all seemed to work properly, eventually.
One problem I encountered with concurrency and NSURLConnection, is the delegate methods not being called. After some research I found out the NSURLConnection needs to either be scheduled in the main runloop, or the NSOperation should be running on the main thread. In the first case I'm invoking NSURLConnection like this:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
And in the latter case like this:
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
The delegate methods handle everything else, and both seem to work properly.
In the case when I was using a dispatch queue, I did the same as with the first case (schedule the NSURLConnection in the main runloop).
My question is, what's the difference between these two approaches? Or are they actually the same, but just a different way of implementing them?
The second question is, why is this necessary? I'm also using an NSXMLParser inside an NSOperation, and this doesn't seem to require a main runloop or main thread, it just works.
I think I figured it out myself. Since both NSURLConnection and NSXMLParser are asynchronous, they require a run loop for the delegate messages when they're running in the background.
As far as I know now, the main thread automatically keeps the run loop running; the main run loop. So both solutions for NSURLConnection I posted will make sure the main run loop is used for the asynchronous part, either by telling the connection to use the main run loop for the delegate messages, or by moving the entire operation onto the main thread, which will automatically schedule the connection on the main thread as well.
What I've come up with now, is to keep a run loop running on my custom NSOperation classes, so I no longer have to perform any scheduling or thread checking. I've implemented the following at the end of the (void)start method:
// Keep running the run loop until all asynchronous operations are completed
while (![self isFinished]) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

Keep NSThread alive and run NSRunLoop on it

So I'm starting a new NSThread that I want to be able to use later by calling performSelector:onThread:.... From how I understand it calling that methods add that call to the runloop on that thread, so on its next iteration it will pop all these calls and subsequently call them until there is nothing left to call. So I need this kind of functionality, an idle thread ready for work that I just can call upon it. My current code looks like this:
- (void)doInitialize
{
mThread = [[NSThread alloc] initWithTarget:self selector:#selector(runThread) object:nil];
[mthread start];
}
- (void)runThread
{
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
// From what I understand from the Google machine is that this call should start the
// runloop on this thread, but it DOESN'T. The thread dies and is un-callable
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
- (void)scheduleSomethingOnThread
{
[self performSelector:#selector(hardWork) onThread:mThread withObject:nil waitUntilDone:NO];
}
But the thread is not kept alive, and the performSelector:onThread does not do anything. How do I go about this the right way?
A run loop requires at least one "input source" to run. The main run loop does, but you have to add a source manually to get a secondary run loop's -run method to do anything. There's some documentation on this here.
One naïve way to get this to work would be just to put [[NSRunLoop currentRunLoop] run] in an infinite loop; when there's something to do, it'll do it, and return immediately otherwise. The problem is that the thread will take a decent amount of processor time simply waiting for something to occur.
Another solution is to install an NSTimer on this run loop to keep it alive.
But, if possible, you should use a mechanism designed for this sort of thing. If possible, you may want to use NSOperationQueue for background operations.
this piece of code should force the thread to wait forever
BOOL shouldKeepRunning = YES; // global
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // adding some input source, that is required for runLoop to runing
while (shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); // starting infinite loop which can be stopped by changing the shouldKeepRunning's value

Unpause this NSTimer?

I am pausing an NSTimer like so:
- (IBAction)pause
{
pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
previousFiringDate = [stopmusictimer fireDate];
NSDate *date = [NSDate distantFuture];
[stopmusictimer setFireDate:date];
}
I have another IBAction:
- (IBAction)unpausebutton
{
// dunno code
}
But I am not sure how to unpause the NSTimer now. Anyone, please help me! Thanks
NSTimer instances weren't really designed to be dynamically mucked with; the setFireDate: explicitly documents that doing so is relatively expensive. Probably similar to just invalidating the old one and creating a new one.
If you are pausing the timer and then your "unpause" is always "fire right now", I'd suggest invalidating and releasing the timer on pause (release only if necessary) then simply calling the targeted method on unpause.
That would be a more typical pattern.

Objective C: App freezes when using a timer

It took me hours to figure out how to implement a timer into my program, but when it runs, the app doesn't load completely as it did before the timer.
In my main.m:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
OutLauncher *theLauncher = [[OutLauncher alloc] init];
NSTimer *theTimer = [theLauncher getTimer];
[theTimer retain];
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
The file OutLauncher is being imported into that, which looks like this:
- (void)doStuff {
NSLog( #"Doing Stuff");
}
- (NSTimer *)getTimer{
NSTimer *theTimer;
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
return [theTimer autorelease];
}
The timer works, the console updates every second with the phrase "doing stuff" but the rest of the program just won't load. It will if I comment out the code I added to int main though
A few things:
You don't need to autorelease the timer you return after setting one up with [NSTimer scheduledTimerWithTimeInterval:] It is already autoreleased.
The timer created via scheduledTimerWithInterval is already added to the default run loop. So you don't need to use the following:
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
In fact, you don't even need to keep a reference to the timer unless you need to cancel it yourself.
Here is what apple has to say about what you are doing in the documentation
run
Puts the receiver into a permanent
loop, during which time it processes
data from all attached input sources.
(void)run Discussion If no input sources or timers are attached to the
run loop, this method exits
immediately; otherwise, it runs the
receiver in the NSDefaultRunLoopMode
by repeatedly invoking
runMode:beforeDate:. In other words,
this method effectively begins an
infinite loop that processes data from
the run loop’s input sources and
timers.
Manually removing all known input
sources and timers from the run loop
is not a guarantee that the run loop
will exit. Mac OS X can install and
remove additional input sources as
needed to process requests targeted at
the receiver’s thread. Those sources
could therefore prevent the run loop
from exiting.
If you want the run loop to terminate,
you shouldn't use this method.
Instead, use one of the other run
methods and also check other arbitrary
conditions of your own, in a loop. A
simple example would be:
BOOL shouldKeepRunning = YES;
// global NSRunLoop *theRL =
[NSRunLoop currentRunLoop]; while
(shouldKeepRunning && [theRL
runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO
somewhere else in the program.
Availability Available in iOS 2.0 and
later.
So it looks like your code is doing what it is supposed to do. It is Logging all the timer events and waiting indefinitely for the run loop.
It looks like you're making it a lot more complicated than it needs to be. You don't need to put any code in your main.m file. If you want to fire the doStuff method every second, this is all the code you need:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
You don't need to (auto)release it yourself. timer is already autoreleased. If you want to be able to cancel the timer, you will need to keep a reference of it. Then when you want to cancel, you just call invalidate and set the reference to nil.