I have a few tabs in my iPhone application which take a few seconds to load (pulling large amounts of data from a local sqlite database). When the users touch the tabs it appears as if the application is doing absolutely nothing. I've attempted to put a window showing a spinner up, however it is never shown due to the fact that the processing immediately follows.
I know i have a couple of different options for loading the data asynchronously, however i wanted to poll the community and see if there were any potential problems with just forcing another cycle of the NSRunloop to show the window.
Here's what my code looks like...
[[ActivityIndicator sharedActivityIndicator] show];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
On a scale of 1 to 10 how bad would you rate this hack?
I don't know where I'd rate it, but I know I wouldn't want to do it that way. Messing with the system default runloop just seems like a bad idea.
There are a couple of what I think are good approaches. The simplest is to put the additional processing in a separate private method, and then do this:
[[ActivityIndicator sharedActivityIndicator] show];
[self performSelector:#selector(processingMethod) withObject:nil afterDelay:0];
That will cause processingMethod to be called at the end of the run loop, after your indicator is showing. Should work fine.
The one caveat is that if your indicator is animated, depending on how it's set up it may not be animated while processingMethod is running. In that case you'd want to run processingMethod in a background thread, which might be a little more complicated, or might be as simple as doing this instead:
[self performSelectorInBackground:#selector(processingMethod) withObject:nil];
The potential complication there is that at the end of processingMethod, when you're going to display the results of your processing, you may have to call a method back onto the main thread.
My experience is that the event handling code on iPhone is not reentrant. So if you're running the runloop in default mode be prepared for various crashes.
I've found others are having issue too:
http://lists.apple.com/archives/xcode-users/2009/Apr/msg00313.html
http://www.iphonedevsdk.com/forum/iphone-sdk-development-advanced-discussion/16246-trying-make-modal-dialog-using-nested-nsrunloop.html
Related
I have a MBProgressHUD that shows when data is being pulled on the background asynchronously. Sometimes when the network is slow this will take forever. So as of now I am just hiding it after 30 seconds if it hasn't been dismissed. What is a good way to dismiss this HUD for a slow network connection?
I would say that the best solution is probably to keep the HUD up the whole time the data is loading so that the user knows that something is happening, and perhaps give them an option to cancel it if that is appropriate for your app. Alternatively, if it is possible for you to load and display the data piecemeal (i.e. before you have the entire set of data), then you should just display the HUD until you have enough data that you can start displaying something in the UI that the user can interact with.
Basically, what you want to avoid is a situation where it could appear to the user that nothing is happening and the UI is essentially blank with nothing for them to do.
Implement MBProgressHUD delegate
it will be called every time whether if it is fast or slow network connection,In case of slow network connection there will be a time out and this delegate will be fired,remove the hud from the superview in this delegate
-(void)hudWasHidden
{
[HUD removeFromSuperview];
}
I’m an experienced C/C++ programmer coming up to speed on Objective C on the iPhone. I have done a lot of searching, but haven’t found a satisfactory answer on what must be a common question; I apologize if this is answered elsewhere, pointers would be appreciated.
My app is very CPU intensive. The UI has a simple display that shows progress and a start/stop button. What is the best way to allocate the most possible CPU cycles to getting the work done, while still ensuring that the display is updated regularly and the start/stop button is responsive? I have read that you should not do work in the main thread, but beyond that I haven’t found many suggestions. In light of this I have implemented my work in an NSOperation queue. I have also put the screen refresh in its own queue. I have also liberally sprinkled the code with NSThread sleepForTimeIntervals. I have experimented with different sleep times from .001 to 1 ([NSThread sleepForTimeIntervals .1] for instance). In spite of this the screen display is sluggish at best (10s of seconds) and pressing the stop button highlights the button but nothing happens again for 10s of seconds.
1.) Are NSOperation Queues a reasonable choice? If not, what else?
2.) How do I minimize the sleeping? (Obviously I want the work to get as many cycles as possible/reasonable, and I’m not sure that my sleeps are doing anything at all to all the UI to update.)
3.) Is there a better technique to keep the UI up to date? For instance, can I use NSTimer or some other method to send a message to the UI telling it to update and/or check the status of the buttons?
Thank you for your support.
1.) Are NSOperation Queues a reasonable choice? If not, what else?
NSOperationQueue sounds like it would be reasonable.
of course, you have choice: pthreads, libdispatch (aka GCD), c++ thread libraries built on top of pthreads, etc, etc , etc. if you don't spawn much/many, then it just comes down to the model you favor.
2.) How do I minimize the sleeping? (Obviously I want the work to get as many cycles as possible/reasonable, and I’m not sure that my sleeps are doing anything at all to all the UI to update.)
don't sleep =) you can use a timer for your ui elements or an explicit callback or notification to notify dependencies. if the dependencies peform ui updates, then you will likely add the message to the main thread's message queue.
3.) Is there a better technique to keep the UI up to date? For instance, can I use NSTimer or some other method to send a message to the UI telling it to update and/or check the status of the buttons?
that really depends on what you are doing. if you merely want to update a progress bar, then you can write the value from the secondary thread and read the value from the main thread. then use a timer on the main run loop to periodically message your object to update its display (based on the current value). for something like an unstaged progress indicator this may be good.
another alternative is more useful for events or stages: it would involve posting updates (e.g. notifications or callbacks to a delegate) from the secondary thread as progress is made (more info under #2).
Update
I wasn't sure this was appropriate in the iOS model, but it sounds like it is.
yes, that's fine - there are many appraches you can take. which is 'best' depends on the context.
My current understanding is to launch the UI in one thread (not the main!),
you really don't explicitly launch the UI; the main thread is (generally) driven by pushing events and messages onto the main thread. the main thread uses a run loop and processes the queued messages/events at each iteration of the run loop. you can also schedule these messages in the future (more on that in a bit). having said that, all your messages to UIKit and AppKit (if you target osx) objects should be on the main thread (as a generalization which you will eventually learn there are exceptions to this). if you have a specific implementation which is completely separated from messaging UIKit objects' methods and that program is thread safe, then you can actually perform those messages from any thread because it does not affect the state of the UIKit implementation. simplest example:
#interface MONView : UIView
#end
#implementation MONView
// ...
- (NSString *)iconImageName { return #"tortoise.png"; } // pure and threadsafe
#end
launch my worker thread, use a timer to generate a signal to the UI to take a look at a progress value and update the progress bar appropriately. For the purposes of this particular application your second to last paragraph is ample and I don't need to go to the lengths of the last paragraph (at least for now). Thank you.
to do this, you can use an approach similar to this:
#interface MONView : UIView
{
NSTimer * timer;
MONAsyncWorker * worker; // << this would be your NSOperation subclass, if you use NSOperation.
}
#end
#implementation MONView
// callback for the operation 'worker' when it completes or is cancelled.
- (void)workerWillExit
{
assert([NSThread isMainThread]); // call on main
// end recurring updates
[self.timer invalidate];
self.timer = nil;
// grab what we need from the worker
self.worker = nil;
// update ui
}
// timer callback
- (void)timerUpdateCallback
{
assert([NSThread isMainThread]); // call on main
assert(self.worker);
double progress = self.worker.progress;
[self updateProgressBar:progress];
}
// controller entry to initiate an operation
- (void)beginDownload:(NSURL *)url
{
assert([NSThread isMainThread]); // call on main
assert(nil == worker); // call only once in view's lifetime
// create worker
worker = [[MONAsyncWorker alloc] initWithURL:url];
[self.operationQueue addOperation:worker];
// configure timer
const NSTimeInterval displayUpdateFrequencyInSeconds = 0.200;
timer = [[NSTimer scheduledTimerWithTimeInterval:displayUpdateFrequencyInSeconds target:self selector:#selector(timerUpdateCallback) userInfo:nil repeats:YES] retain];
}
#end
note that this is a very primitive demonstration. it's also more common to put the timer, update handling, and operation in the view's controller, not the view.
Are you doing your UI updates on the main thread? This is very important because UIKit is not thread-safe and using it from a secondary thread can lead to sluggish behavior (or crashes for that matter). You usually should not need to use sleep in your background threads/queues for the UI to remain responsive (unless your UI itself is very CPU-intensive but that doesn't seem to be the case here).
You can check any of your methods that update the UI if they are running on the main thread with something like
NSAssert([NSThread isMainThread], #"UI update not running on main thread");
An easy and lightweight way to synchronize UI updates with the main thread is to use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^ {
//do your UI updates here...
});
Here you are my answers to your questions.
1) Since you are an experienced C programmer, you will feel comfortable with Grand Central Dispatch (GCD), a C based API for concurrency.
2) With GCD, you do not need to sleep at all. Simply dispatch asynchronously the work you need to do in a queue using the maximum priority (DISPATCH_QUEUE_PRIORITY_HIGH).
3) When you need to update the UI, simply dispatch on the main queue ( within the same block doing the work, using dispatch_get_main_queue() ) the UI update as needed.
Take a look at the relevant GCD documentation here.
I'd have a model object that does the CPU tasks, which has a delegate callback for when the output changes, and a view controller. In viewDidLoad you set the view controller as the delegate of your model. The model, therefore, can use threads and sends messages back on the main queue, when the calculated data has been updated. Unless your case is specifically complex, is just use Grand Central Dispatch and dispatch_async the intensive task onto another thread.
Certainly, you should not be calling sleepForTimeInterval anywhere to achieve what you want.
I have a button which when pressed makes calls that load information from a series of URLs (about 5 seconds loading time). Right before actually making those calls I want to add a "loading" alert. When I use a UIAlertView the screen dims like it's about to pop up, but it doesn't until the data is loaded - too late! I have no idea what's going on, it's like the calls I'm making to load the data are immediately taking preference over showing the new view, even though they're made right after the calls adding the new view (or showing the alert).
This is a summarized version of the code:
-(void) refresh{
UIAlertView *av = ...
[av show]; //this should pop up before dat begins to load
[myDataSource loadData]; //this contains a series of [NSData initWithURL] calls
[self.tableView reloadData];
//here I would hide the AlertView, but if I do I see it for just s split second
//when the tableView has already reloaded
}
Thanks in advance for any insight!
***EDIT
To anyone who uses performSelectorInBackground beware of the added complexities of creating what is effectively a threaded program. For example, leaks might appear as the new thread doesn't have an autorelease pool - you have to add one, etc.
when you try to retrieve data from the internet (I guess you are using something like [NSString stringWithContentsOfURL: ...]) the main thread is waiting for those data and for this reason the application cannot redraw the interface.
You can try to use:
[mySourceData performSelectorInBackground:#selector(loadData) withObject:nil];
Be careful if you are using coredata to lock the NSManagedObjectContext before doing any operation on it and unlock it when you finish.
If you have a bunch of operations to perform, in addition to performSelectorInBackground: like cescofry wrote, you can use NSOperation and NSOperationQueue.
You probably don't want an UIAlertView for this anyway. Take a look at MBProgressHUD on GitHub.
In my iPhone app, I have to perform function constantly in background.
For that I think I will have to use NSThread to call the function and keep it executing in background.
I dont want to stall my app and hence I want to use NSThread to keep my Main Thread free for user interaction.
How should I implement NSThread to perform the function in background?
EDIT:
The function is for fetching the data from a web server every 20 seconds and updating the tables in my iPhone app based on the data that is fetched from the web server.
I'd look at an NSOperationQueue first.
I'm guessing that your background task is really a small task repeated again and again. Make this into an NSOperation subclass and just add them onto an NSOperationQueue. That way you can control the background tasks more easily.
You also get the advantage with an NSOperationQueue that when there are no operations to run, the processor isn't just stuck in a while(YES) loop, waiting. This will help your app's UI be more responsive and will help battery life :)
However, if your background task is a single long running task that just needs to be started and then ignored, performSelectorInBackground isn't too bad an idea.
Sounds like a bad idea, but it's very simple.
[self performSelectorInBackground:#selector(theMethod:) withObject:nil];
Just have a while(YES) in theMethod: and it will never stop executing.
EDIT:
Luckily for you it's just as simple to do something once every 20 seconds.
[NSTimer scheduledTimerWithTimeInterval:20 target:self selector:#selector(theMethod:) userInfo:nil repeats:YES];
This will execute theMethod: once every 20 seconds. I might also add that this is a much better idea.
you'll want to interact with the thread's run loop. if it's an NSThread, it is created automatically. thus, you are given access to the CF/NS-RunLoop - typically by calling + [NSThread currentRunLoop] from the secondary thread.
in that case, you add a CF/NS-Timer to the run loop, and let it run and repeat until your work is finished. when the timer fires, your thread is awoken, and you then do your work.
I am currently working on a project where I request and parse multiple html sites in a controller. To give some feedback to the user I have created a second view, which gets displayed during processing of the data. It displays a status label and a progressbar. In my controller I have several points where I update my labels text. Unfortunately this works only sometimes. I guess thats because the label gets redrawn only once in a while and I try to do it probably more often than once a second.
It does not work to force a redraw with [label setNeedsDisplay];
I also made a thread which updates the labels text with the text of a global variable and also calls setNeedsDisplay on the label every second. But the results in the same. I only see certain changes, but not all.
Everything is setup properly and the label never is nil. When I log my updateMethod everything seems allright. It just does not get displayed!
Cheers
Here is the code of my threads
- (void)startUpdateStatusThread{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateFrequently) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateFrequently{
NSLog(#"updateFrequently: %#", currentStatus);
test++;
[self.statusLabel setText:[NSString stringWithFormat:#"%# - %i", currentStatus, test]];
[self.statusLabel setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:NO];
}
Am I right if I assume that you call your label's text-property as well as setNeedsDisplay from within your "Thread" that is parsing your websites?
Note that: changes to UIViews (or subclasses of UIView like your label) must be performed on the main thread.
What I recommend you to do is write a helper method that updates your label and calls setNeedDisplay, the from within your Parser-Thread call
[mainViewController performSelectorOnMainThread:#selector(yourUpdateLabelHelper:) withObject:nil waitUntilDone:NO];
that should get your job done.
You mention threads. Be aware that UIKit controls, such as UILabel, can only be updated from the main thread. Make sure you are only attempting to set the label's text from the main thread and then verify if you are still having issues.
EDIT due to question edit:
First, -setNeedsDisplay only tells the view it needs to redisplay the next time the screen is refreshed, it does not force a display at that time.
Second, the iPhone screen draws at about 60 hz. This means there is an update every 0.016666 or so seconds. You're trying to update much faster than that, so only about every 160th of your updates will actually be displayed.
Third, and probably most important, you don't seem to be making any threads, unless you call -startUpdateStatusThread from a background thread. Either way, once you hit -updateFrequently everything is back on the main thread. With the frequency you are scheduling that timer, you are probably overloading the CPU and making it so that the system does not have time to draw the label. So even though you are setting new text, you aren't giving it a chance to render. You need to reduce the frequency of that timer, and you probably need to rethink whatever it is you're trying to do.
I think that the creation of the timer should be in separate function:
Allow the repetition and also store the timer in a member variable you can close on dealloc.
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:YES];
}
The update function should operate as a callback function.
Regards
Assayag