I need to show an Activity Indicator to the user during a database operation that takes some seconds.
I have the UIActivityIndicator configured and working, but when I call [myActivity startAnimating]; and the next call is to do the database operations it never shows me the activity.
I think this could be solved by doing an asynchronously access to the database, but I don't know how to do this.
Thank for any related information.
The Main UI is probably freezing when you execute your database query, hence the frozen animation.
How about running the database operation in a background thread:
[myActivity startAnimating];
[self performSelectorInBackground:#selector(someMethod) withObject:nil];
-(void)someMethod {
// do something in the background here.
// long running task
[myActivity performSelectorOnMainThread:#selector(stopAnimating) withObject:nil];
}
One thing to note, if you use Core Data on the background thread to update date you must synchronize them so the changes appear on the main thread (read up on Core Data / Threading for more info)
Related
I am using FMDatabase for sqlite based iphone application. The problem is that application is fetching bulk data from a web service and inserting into a local sqlite database which is blocking UI [main thread]. Also we cannot run sqlite related commands in background thread. Can we use NSOperation here ? Any example ??
You should be able to run your SQLite operations in the background, as long as you only run them inside that thread and not from the main or any other.
You could use a NSOperationQueue to handle this, setting the max number of concurrent operations to 1 to make sure only one writes to your SQLite at a time and then calling NSInvocationOperations to save your data.
NSInvocationOperation * invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(writeThisToDB) object:thisObject];
[operationQueue addOperation:invocation];
Imagine I have a spinner that I have to enable while something relatively heavy is being done and then deactivate the spinner after the task is done.
If I do:
[mySpinner startAnimating];
[self doSomethingHeavy];
[mySpinner stopAnimating];
I will never see the spinner running, because doSomethingHeavy will lock the thread and never let the spinner show.
I have tried to fire a new queue on the main thread using Grand Central Dispatch for the spinner and in another try for the task, but the results are the same. No spinner running.
The only way to make it work is to fire the method with a delay, using
[self performSelector:#selector(doSomethingHeavy) withObject:nil afterDelay:0.02];
but this sounds more like a hack and if I put [mySpinner stopAnimating] after that line, it will probably stop the spinner before the task is done.
This is not just valid for the spinner but for any task that that needs screen update.
...
[mySpinner startAnimating];
[self performSelectorInBackground:#selector(doSomethingHeavy) withObject:nil];
...
}
- (void)doSomethingHeavy {
...
[mySpinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
Or instead of stopping the spinner in doSomethingHeavy it would more likley finish with a call to:
[self performSelectorOnMainThread:#selector(finishedSomethingHeavy) withObject:nil waitUntilDone:NO];
which would stop the spinner and update the UI with the heavy results.
Hopefully I'm understanding the question properly, but normally I'd use the method
[self performSelectorInBackground:#selector(doSomethingHeavy) withObject:nil];
in this situation as this leaves the main thread free to update the UI while still performing the task in the background.
I have tried these solutions but unfortunately none worked, because my doSomethingHeavy method must be run on the main thread too.
Using the hack of firing the method with a delay works but not for methods that should run with more than one parameter, as performSelector: afterDelay: cannot be used to pass more than one parameter.
I found a solution firing a queue on the main thread using Grand Central Dispatch and then sleeping it for 0.02 seconds. Using the queue, I can put anything I want.
Basically, i have a method that takes a few seconds to complete as it copies some files using NSFileManager. This is invoked on the touchesMoved event when the user picks up a draggable UIView icon. However, there's a slight delay before the icon's position is updated. I'm guessing it's waiting for that method to copy it's files before continuing. The method HAS to be triggered on touchesMoved, so please don't suggest moving it.
How can i execute a method that takes about a second to complete, without holding up the code?
(..and don't worry the copy method doesn't get repeatedly called from the touchesMoved event)
You could perform the task in the background using performSelectorInBackground:...:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html
This prevent that selector from blocking the main thread.
Example:
[self performSelectorInBackground:#selector(myMethod) withObject:nil];
Do it in a background thread. Leave the main thread to deal with UI stuff only.
Technically you could divide the copying of files into very small chunks, and tell the current NSRunLoop to dispatch between each file copy.
But practically just say no to any IO access on the main thread, all IO access should be done in the background. Even the slightest block on the main thread will make the UI stutter and be unresponsive, Android user might accept that, iOS user do not.
Your options are numerous, and easy to implement. You could do a simple performSelector–:
-(void)backgroundWorker {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
}
-(void)startDoingIOStuff {
[self performSelectorInBackground:#selector(backgroundWorker)
withObject:nil];
}
You could do it practically inline using a block and GCD:
-(void)startDoingIOStuff {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL),
^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
});
}
Or you could use an NSOperation on a NSOperationQueue. I have written a longer blog post on this topic, including source code that is available here: http://blog.jayway.com/2010/08/19/future-cocoa-operation/
Before immediately resorting to a secondary thread, it would certainly be worth a try to use a plain old performSelector on self. For example:
[self peformSelector:#selector(copyFiles) withObject:nil afterDelay:0.0];
Note that this is different from doing:
[self copyFiles];
The peformSelector version basically says "do copyFiles ASAP, OK?", but doesn't block everything while waiting for it to be done. In other words, it's possible that the perform selector version would allow the main event loop to update the UI (thereby preventing the apparent visual lag) before the file copying is actually done.
I had the UIActivityIndicatorView working fine in simulator and other 3.0 devices in my app. But I found out that it was not spinning (or showing) in the new iphone 4. Basically I need to show the activity indicator when a button is clicked and hide it when the button click event is complete. I was using the approach below.
[NSThread detachNewThreadSelector: #selector(spinBegin) toTarget:self withObject:nil];
from this link. As mentioned, it correctly spins the activity indicator on all except 4.*.. not sure why. To get around this, I also followed another approach something like (from developer.apple.com)
`
(IBAction)syncOnThreadAction:(id)sender
{
[self willStartJob];
[self performSelectorInBackground:
#selector(inThreadStartDoJob:)
withObject:theJobToDo
];
}
(void)inThreadStartDoJob:(id)theJobToDo
{
NSAutoreleasePool * pool;
NSString * status;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
status = [... do long running job specified by theJobToDo ...]
[self performSelectorOnMainThread:
#selector(didStopJobWithStatus:)
withObject:status
waitUntilDone:NO
];
[pool drain];
}
`
The problem with this was that, it is showing the acitivityVIewIndicator spinning correctly (at least on the simulator) but after it stops, the built in activity indicator in the top bar (where it shows the battery% etc) is still spinning.
I'm new to objective C. I have finished my app completely but for this silly thing. I realize there is no way to display UIActivityView without starting another thread. and finally, just to rant, I don't understand why they have to make it so complicated. I mean they knew it was going to have this problem, why not provide a sample code everyone can use rather than deriving their own solutions.
Finally, can anyone please provide me with a direction or some sample code. I would really appreciate it. I have been searching for a few hours now and have not found anything really that works!
Why are you starting/stopping the indicator on a separate thread? Any methods you send to your UIActivityIndicatorView must be sent on the main (UI) thread.
Any events sent by a button pressed will automatically be run on the main thread. If you're using background threads to complete the process, you could do something like:
- (IBAction)buttonPressed:(id)sender {
// This runs on the main thread
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(inThreadStartDoJob:) withObject:theJobToDo];
}
- (void)inThreadStartDoJob:(id)theJobToDo {
// Set up autorelease pool
...
// Run your long-running action
...
// Stop the spinner. Since we're in a background thread,
// we need to push this to the UI Thread
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
Edit: As for the activity indicator in the top bar (where the battery is), doesn't this automatically start/stop based on network activity?
I have an iPhone app, where I'm displaying a tableview, that's loaded from an RSS feed. When the view is loaded, I call this method to run in a new NSThread:
- (void)start:(NSURL*)url {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSXMLParser *XMLParser = [[[NSXMLParser alloc] initWithContentsOfURL:url] autorelease];
[XMLParser setDelegate:self];
if (items) {
[items release];
}
items = [[NSMutableArray alloc] init];
[self startParsing:XMLParser];
[pool drain];
}
It's working fine, but if the user leaves the view while it's downloading or parsing the xml, I want the thread to stop running, but how would I stop it from running without leaking memory? Also, if it's running the -initWithContentsOfURL: method while I want it to stop, how would I stop that method?
If you anticipate needing to control connections (i.e. stopping a connection if the user cancels or navigates away) you should probably use the asynchronous NSURLConnection API to load your data before parsing the XML. In addition to giving you the ability to close connections as needed, you'll also be able to better respond to network errors.
As NSD pointed out, you should probably implement some sort of cancel method on the class that's driving your XML parsing thread - then just use performSelector:onThread:withObject:waitUntilDone: (or similar) from your main thread when the user cancels the download or navigates away.
These are your thread stopping options
http://developer.apple.com/mac/library/documentation/cocoa/reference/Foundation/Classes/NSThread_Class/Reference/Reference.html#//apple_ref/doc/uid/20000311-DontLinkElementID_12
And from elsewhere in the guide
"If you anticipate the need to terminate a thread in the middle of an operation, you should design your threads from the outset to respond to a cancel or exit message."
Perhaps you should look into the NSOperation and NSOperationQueue classes.
These classes give you a massive amount of control over concurrency and asynchronous execution.
The basic idea is to create a queue, and then subclass NSOperation. Inside your subclasses' main method, do the guts of your work, in this case, you could put your start method inside here.
Then you can control the operation easily, being able to set how many operations can run concurrently, set up any dependencies some operations may have on others. You can also easily cancel operations, which is what you want to do here.
Check out the documentation for NSOperation and NSOperationQueue.