I am calling a method like methodA in background.now if i call a lengthy method called methodB from methodA.should i separately mention it to be in background.the reason i ask this question is inspite of calling the lengthy process in background thread,the ui hangs for some time.
ie
[self performSelectorInBackground:#selector(methodA)];
-(void)methodA
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
[self methodB];
[pool drain];
}
-(void)methodB
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
//some lengthy process
[self performSelectorOnMainThread:#selector(updateTable) withObject:nil waitUntilDone:NO];
[pool drain];
}
-(void)updateTable
{
[self.tableview reloadData];
}
is this way of calling background method right?
If a selector (method) X is called on a certain thread (whether it be background or main thread), any selectors that X calls (in the conventional fashion) are also on that same thread. So no, you don't need to call performSelectorInBackground: for each sub-call from methodA: as long as the entry-point selector is on the 'correct' thread, anything it then does is also on the 'correct' thread, including calls to other methods.
Note that the NSAutoreleasePool you set up in methodB looks unnecessary -- you don't really need it, since you're already inside the scope of the NSAutoreleasePool set up in methodA. (Assuming that methodB is only called from methodA as in the example!)
Incidently, have you put in NSLogs to absolutely verify that //some lengthy process is actually the thing taking all the time?
Related
I have an app that needs to signal continuously a word in morse code. I did this by creating an NSThread and running some code inside the selector with a "while loop". Here is the code:
#implementation MorseCode
-(void)startContinuousMorseBroadcast:(NSString *)words{
if (!(threadIsOn)) {
threadIsOn = YES; s
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadSelector:) object:words];
[myThread start];
}
if (morseIsOn) {
morseIsOn = NO;
}
else{
morseIsOn = YES;
}
}
-(void)threadSelector:(NSString *)words{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (![myThread isCancelled]) {
// ///it Does some code here
} //end While
NSLog(#"Cleaning the pool");
[pool drain];
}
#end
When exiting the application (the user presses the button), in the applicationDidEnterBackground the following selector is executed:
-(void)cleanUpMorseObject{ //this is defined in the MorseCode class, same as threadSelector
if (threadIsOn) {
NSLog(#"cleanUpMorseObject, threadIsOn");
threadIsOn = NO;
morseIsOn = NO;
[myThread cancel];
[myThread release];
}
}
The application responds correctly to the event, I’ve checked with nslog.
And then [MorseCode release] is called.
The code looks like this:
-(void)applicationDidEnterBackground{ //this happens in the ViewController
[theMorse cleanUpMorseObject]; //theMorse is an instance of MorseCode
[theMorse release];
}
The problem: Although I call [myThread release] and then [theMorse release] the retainCount of the theMorse is still above 0 (It doesn’t call the dealloc).
The Leaks Instrument doesn’t say I have a leak, but if I open and close the application for like 10 times eventually the Iphone resets. Also in the debugger eventually I see the “Received memory warning. Level=2”.
Also I never see the NSLog before the pool drain…
The app doesn't run in the background.
Any ideas? Thank you
You really should schedule the sending of the message on the RunLoop, the probably easiest way being to schedule a timer (repeat infinitely, and short repeat period like FLT_EPSILON or similar) instead of using threads for that.
Working with threads is complicated and as everyone should avoid it (as Apple stated in its Concurrency Programming Guide, and as most documentation said, "Threads are evil" ;)).
That's because multithreading is a vast and complicated subject, that needs synchronizations, resources protection, being aware of dead locks, critical sections & so on, good and adapted memory mgmt, and much much more. In general if you need to do stuff in the background:
Use mechanisms already in place (like asynchronous implementation of some operations and being signalled by delegate methods or notifications) if available
Use methods like performInBackground:
Use NSOperationQueues
Use GCD
And only in last resort and if there are no other options (or for really specific cases), use NSThread.
This will avoid you a lot of issues as all the other, higher APIs will take care of a lot of things for you.
Moreover, using threads for this task like you do is likely to use much more CPU (will probably reach 100% usage quickly) as there won't be any time left for the task scheduler (that also why even GCD that takes care of all stuff like that is way better than NSThreads, and scheduling the sending in the RunLoop is even better for the CPU if you don't need strong RT constraints)
First, retainCount can never return 0. It is a useless method. Don't call it.
Secondly, leaks only detects objects that are no longer referenced. If a thread is still running, it isn't leaked.
Finally, a thread doesn't stop when you call cancel. It just sets a flag that you have to check via isCancelled to see if it is time to stop work in the thread. Are you doing that?
OK -- easy stuff answered. Next? Try build and analyze. Then use the Allocations instrument and turn on reference count tracking. Then see what is calling retain an extra time.
I decided to give up the NSThread class and used another aproach:
-(void)playSOSMorse{
if ([myTimer isValid]) {
[myTimer invalidate];
[myTimer release];
myTimer = nil;
}
myTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(tymerSelector)
userInfo:nil
repeats:NO] retain];
//the timer calls a selector that performs a selector in background
}
-(void)tymerSelector{
[self performSelectorInBackground:#selector(threadSelector2) withObject:nil];
}
-(void)threadSelector2 {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//some code here
[pool drain];
//calls another selector on the main thread to see if it needs to fire the timer again and restart the cycle
[self performSelectorOnMainThread:#selector(selectorOnMainThread) withObject:nil waitUntilDone:NO];
}
-(void)selectorOnMainThread{
[myTimer invalidate];
[myTimer release];
myTimer = nil;
if (morseIsOn) { //this is a boolean that if it is true (YES) calls the timer again
[self playSOSMorse];
}
}
I hope this helps somebody :)
Thank you
I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.
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
Goal is to "launch a spinner graphic at start of viewWillAppear that loads data before showing the tableview" so the user doesn't wonder why there's a delay before seeing the table. I.e. a UIActivityIndicatorView has been added to the window, and I just want to set the alpha to hide/show it.
I get this strange error when starting a thread to make sure the "spinning gears" imageview (tag=333) gets shown, before moving on to load/calculate stuff in viewWillAppear.
I don't get it on every call to [appdel addGearz] and [appdel removeGearz], it happens for both these, and it's random. It can happen after 2 viewWillAppears, or after 15. If I comment out the line that sets the alpha, everything works.
A typical viewWillAppear looks something like this,
[super viewWillappear];
self.title=#"Products listing"; //and other simple things
[appdel addGearz];
[self getProducts];
[self getThumbnails];
[myTableView reloadData]; //in case view already loaded and coming back from subview and data changed
And here is the code that crashes if the lines with .alpha are not commented out
-(void)addGearz {
[NSThread detachNewThreadSelector:#selector(gearzOn) toTarget:self withObject:nil];
}
-(void)removeGearz {
[NSThread detachNewThreadSelector:#selector(gearzOff) toTarget:self withObject:nil];
}
- (void)gearzOn {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[window viewWithTag:333].alpha=1.0;
//
// [[window viewWithTag:333] setNeedsDisplay];
[pool drain];
}
- (void) gearzOff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[window viewWithTag:333].alpha=0.0;
//
// [[window viewWithTag:333] setNeedsDisplay];
[pool drain];
}
I've used someone else's code, so... anything obvious you can see? Surely I must be able to change alpha of UIViews in a thread? Do I need to "embed" the alpha-change in some "stop enumerating while I change this"-code?
I made it not crash by moving that alpha-change-line to above the pool alloc or below the [pool drain], but then I get a lot of "autoreleased with no pool in place - just leaking"-messages.
Apparently, there's something I don't understand about this thread code.
You must not try to modify the UI on a separate thread. UI should only be manipulated on the main thread.
Instead of detaching a new thread, you should use performSelectorOnMainThread:withObject:waitUntilDone:. This will ensure that the method will be called on the proper thread.
I'm trying to add a progress meter, or other "I'm busy right now" notification to my view hierarchy right before doing some intense computation that will block the UI. My code looks some thing like:
//create view
[currentTopView addSubView:imBusyView];
//some initialization for the intense computation
[computation startComputing];
Unfortunately, my progress meter doesn't display until after the computation completes. It appears like the views aren't re-drawn until the run loop completes. I'm pretty sure that setNeedsDisplay and setNeedsLayout will also wait until the run loop completes.
How do I get the view to display immediately?
Redrawing only occurs when your code returns control to the run loop. So the easiest way would be for you to schedule the startComputing call with a zero delay. That way, it will be executed during the next run loop iteration (right after redrawing):
[computation performSelector:#selector(startComputing) withObject:nil afterDelay:0.0];
Be aware, though, that unless you do your computation in another thread you will not be able to update the UI during the computation.
If you are doing heavy calculations maybe spawning a new thread is a good idea.
Here I have an activityIndicator displayed and starts a large XMLParse operation in a background thread:
- (void) setSearchParser {
activityIndicator = [[ActivityIndicatorView alloc] initWithActivity];
[self.view addSubview:activityIndicator];
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
}
then the getSearchResults method:
- (void) getSearchResults: (SearchResultParser *) parser {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[parser startParser];
[self performSelectorOnMainThread:#selector(searchResultsReady:) withObject:[parser data] waitUntilDone:NO];
[pool release];
}
So firstly make a new thread:
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
this means that all code inside the getSearchResults will be executed on a different thread. getSearchResults also get's passed a parameter of "searchParser" thats a large object that just needs startParse called on it to begin.
This is done in getSearchResults. When the [parser startParser] is done, the results is passed back to the main thread method called "searchResultsReady" and the threads autorelease pool is released.
All the time it took from my parser began to it had finished, a gray view covered the screen an an activityIndicator ran.
You can have the small activityIndicator class I wrote:
-(id) initWithActivity {
[self initWithFrame:[self bounds]];
[self setBackgroundColor:[UIColor blackColor]];
[self setAlpha:0.8];
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.center = CGPointMake(160, 240);
[self addSubview:activityView ];
[activityView startAnimating];
return self;
}
- (void) dealloc {
[activityView release];
[super dealloc];
}
Hope it helps you out, even though threads seems a bit confusing, they can help to make the UI not freeze up, which is especially important on the iPhone.