I have an app that I am refactoring and I just implemented multithreading so that the UI may run smoother. In the iphone simulator I don't get any leaks but testing on my iPhone 3G running on iOS 4.2 I get a memory leak. I have done a lot of searching for the correct way to implement an autoreleasepool with an operationqueue, help will be greatly appreciated.
I have created an nsoperationqueue in my viewcontroller as such
- (void)loadData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSOperationQueue *queue = [NSOperationQueue new]; // creates multithread for loading data without slowing UI
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(firstRun) object:nil];
[queue addOperation:operation];
[operation release];
[queue release];
[pool release];
}
First, you shouldn't just create and then release the queue. It's more natural to create that queue as one of your class's ivars and then release it when your view controller goes away (you can also cancel up any pending operations and cancel/wait for any running operations to complete).
Second, you don't need the autorelease pool in the method that creates the operation and adds it in the queue since that method is being called from the main thread. Instead, you need the autorelease pool from the method your object actually calls (this is what may be running on another thread).
So, you might have the following (assuming you name your queue ivar queue_):
- (void)viewDidLoad
{
[super viewDidLoad];
if( !queue_ ) queue_ = [[NSOperationQueue alloc] init];
// other view loading code
}
- (void)loadData
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(firstRun) object:nil];
[queue_ addOperation:operation];
[operation release];
}
- (void)firstRun
{
// Here we may run on another thread, so 1) we need an autorelease pool; and
// 2) we need to make sure we don't do anything that requires a runloop
NSAutoreleasePool* threadPool = [NSAutoreleasePool new];
// do long-running things
[threadPool drain];
}
- (void)dealloc
{
if( queue_ ) {
[queue_ setSuspended:YES];
[queue_ cancelAllOperations];
// you need to decide if you need to handle running operations
// reasonably, but don't wait here because that may block the
// main thread
[queue_ release];
}
// other dealloc stuff
[super dealloc];
}
You could also initialize your queue on demand, so instead of initializing in viewDidLoad, check for its existence and initialize if necessary anywhere you'd add an operation. This might be wrapped in a method call of its own, but lazy initialization here probably isn't really necessary as queues are pretty light-weight.
You should create an NSAutoreleasePool at the start of the method that the NSOperation will invoke (in this case, firstRun), and drain it at the end of the method.
Related
I'm doing an app that loads the contents of viewControllers using NSThread while is reading an XML file.
I have it done as follows:
-(void)viewDidAppear:(BOOL)animated
{
// Some code...
[NSThread detachNewThreadSelector:#selector(loadXML) toTarget:self withObject:nil];
[super viewDidAppear:YES];
}
-(void)loadXML{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Read XML, create objects...
[pool release];
}
My problem is that I don't know how to stop the NSThread if the user changes to another viewController while the NSThread is loading, doing that the app crashes.
I've tried to cancel or exit the NSThread as follows but without success:
-(void)viewsDidDisappear:(BOOL)animated{
[NSThread cancel];
// or [NSThread exit];
[super viewDidDisappear:YES];
}
Can anyone help? Thanks.
When you detach new thread, you can no more cancel or exit it from viewDidDisappear etc. These UI specific methods execute only on main thread so the exit/cancel applies to the main thread which is obviously wrong.
Instead of using the detach new thread method, declare NSThread variable in .h and initialize it using initWithTarget: selector: object: method and cancel it whenever/wherever you want to..
you can also use [NSThread exit]; method of NSThread.
It's better to let a thread end gracefully, i.e. reach its natural conclusion, if you can. It sounds like in your case you can afford to. Also be sure that you're updating the user interface from the main thread, not a secondary thread, as UIKit is not thread safe.
You wrote:
... the app stops responding while the thread finishes...
Once you flag a thread for cancelling or exit, you have to manually stop whatever the thread was called to do. An example:
....
- (void) doCalculation{
/* Do your calculation here */
}
- (void) calculationThreadEntry{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSUInteger counter = 0;
while ([[NSThread currentThread] isCancelled] == NO){
[self doCalculation];
counter++;
if (counter >= 1000){ break;
} }
[pool release]; }
application:(UIApplication *)application
- (BOOL)
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
/* Start the thread */
[NSThread detachNewThreadSelector:#selector(calculationThreadEntry)
toTarget:self withObject:nil];
// Override point for customization after application launch. [self.window makeKeyAndVisible];
return YES;
}
In this example, the loop is conditioned on the thread being in a non-cancelled state.
One of my custom class has NSThread* as member variable.
I'm letting the thread exit by setting a variable(isNeedToExit).
Then call a release on the thread object [myThread release];
I intended the thread to exit first and release call gets called.
But on second thought, release call might get called before the thread notices the boolean value changed and exit.
(Because the thread is not constantly looking at the value to see if it needs to exit)
#property (nonatomic, retain) NSThread* myThread;
- (void) dealloc
{
...
[myThread release];
myThread = nil;
...
[super dealloc];
}
- (id) initMyClass
{
if(self = [super init] )
{
...
NSThread* aThread = [[NSThread alloc] initWithTarget: self selector:#selector(theThreadMain) object: nil];
self.myThread = aThread;
[aThread release];
[self.myThread start];
..
}
return self;
}
- (void) theThreadMain
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Add your sources or timers to the run loop and do any other setup.
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
}
while (self.isNeedToExit == false);
[pool release];
SYSLOG(LOG_DEBUG, "thread exiting");
}
I thought of moving [myThread release] call (now at the class's dealloc) to last line of theThreadMain..
Not sure if this is the correct way to stop a thread when it is a member variable of another class.
Thank you
Actually, your initial assumption was more correct. Your current implementation, however, will not work because dealloc will never be called.
Your class is retained by NSThread in initWithTarget:selector:object and will not be released until the thread finishes executing. Therefore, your class instance will not be deallocated as long as the thread is executing.
One way to solve the problem is to add a "cancel" method to your class that stops the thread. E.g.:
- (void)cancel
{
isNeedToExit = YES;
[myThread release];
myThread = nil;
}
That will cause the thread to stop and will allow your class to be deallocated (once the thread stops.) You need to ensure that all users of your class know that "cancel" needs to be called before the class is released. E.g.:
[myObject cancel]; // Stop the thread
[myObject release];
myObject = nil;
A more traditional solution would be to simply subclass NSThread.
Hi I'm usuing [NSThread detachNewThreadSelector:toTarget:withObject:] and I'm getting a lot of memory leaks because I have no autorelease pool set up for the detached thread. I'm just wondering where I actualy do this? Is it before I call
[NSThread detachNewThreadSelector:toTarget:withObject:]
or in the method that is being ran in the other thread?
Any help would be appreciated, some sample code would be great.
Thanks.
in the method you call with the thread... i.e. given this...
[NSThread detachNewThreadSelector:#selector(doStuff) toTarget:self withObject:nil];
Your method would be...
- (void)doStuff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Do stuff
[pool release];
}
You have to set up an autorelease pool in the method you call and that will be executed in the new detached thread.
For example:
// Create a new thread, to execute the method myMethod
[NSThread detachNewThreadSelector:#selector(myMethod) toTarget:self withObject:nil];
and
- (void) myMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Here, add your own code
// ...
[pool drain];
}
Note that we use drain and not release on the autoreleasepool. On iOS, it has no difference. On Mac OS X, if your app is garbage collected, it will triggers garbage collection. This allows you to write code that you can re-use more easily.
Create the new thread:
[NSThread detachNewThreadSelector:#selector(myMethod) toTarget:self withObject:nil];
Create the method that is called by the new thread.
- (void)myMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Your Code
[pool release];
}
What if you need to do something to the main thread from inside your new thread (for example, show a loading symbol)? Use performSelectorOnMainThread.
[self performSelectorOnMainThread:#selector(myMethod) withObject:nil waitUntilDone:false];
Refer :- iPhone SDK Examples
Do it in the method you call. Essentially, you should set up the method that gets called as a self-contained work unit (in fact, it will then be compatible with being called through either [NSOperation][1] or Grand Central Dispatch, too: both better ways of organising concurrent work).
But what if I can't change the implementation of the method I'm calling on a new thread?
In that case, you would go from doing this:
[NSThread detachNewThreadSelector: #selector(blah:) toTarget: obj withObject: arg]
to doing this:
[NSThread detachNewThreadSelector: #selector(invokeBlah:) toTarget: self withObject: dict]
- (void)invokeBlah: (id)dict {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [dict objectForKey: #"target"];
id arg = [dict objectForKey: #"argument"];
[obj blah: arg];
[pool release];
}
rather than using the dictionary, you could also create an NSInvocation that encapsulates the remote object call. I just chose a dictionary because it's the quickest way to show the situation in a SO answer. Either would work.
The documentation states that the method run in the thread must create and destroy its own autorelease pool. So if your code has
[NSThread detachNewThreadSelector:#selector(doThings) toTarget:self withObject:nil];
The method should be
- (void)doThings {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Do your things here
[pool release];
}
I am trying to implement search on a background thread using NSOperation on iOS. I didn't want to subclass NSOperation so this is what I'm doing:
[searchQueue cancelAllOperations];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
elector:#selector(filterContentForSearchText:)
object:self.searchDisplayController.searchBar.text];
[searchQueue addOperation:op];
[op release];
The search method includes a for loop that checks whether what is being searched is in an array. Now when I cancel the NSOperation by calling cancelAllOperations, the for loop continues to run through the array. I would like to prevent this and was wondering whether it is legit to call this from within the for loop:
if ([[[searchQueue operations] objectAtIndex:0] isCancelled]) {
[tmp_array release]; // tmp_array is used to hold temporary results
[pool drain]; // pool is my autorelease pool
return;
}
One of the reasons to subclass NSOperation is to implement proper cancellation. You could do your approach, but it violates several good design principles. Basically, since cancellation requires the cooperation of the operation itself, NSInvocationOperation isn't built to cancel the invocation while it's already executing (though it can be successfully cancelled before it starts executing), as the running method shouldn't know anything about how it's called.
Instead, if you subclass NSOperation, you can put most of this functionality into the main method very easily:
#implementation MyOperation
- (void)main {
if ([self isCancelled])
return;
for (...) {
// do stuff
if ([self isCancelled]) {
[tmp_array release];
return;
}
}
}
#end
Note also that you don't have to maintain your own autorelease pool with such an implementation.
I have a few method calls like this:
[self myFoo];
[self heavyStuff]; // this one in other thread
[self myBar];
which classes / methods do I have to look at? When I search for "thread", there comes a lot of classes, methods and functions. Which one's are most apropriate here?
You would do
[self performSelectorInBackground:#selector(heavyStuff) withObject:nil];
See the NSObject reference on Apple's site.
For "fire and forget", try [self performSelectorInBackground:#selector(heavyStuff) withObject:nil]. If you have more than one operation like this, you may want to check out NSOperation and its subclass NSInvocationOperation. NSOperationQueue managed thread pooling, number of concurrently executing operations and includes notifications or blocking methods to tell you when all operations complete:
[self myFoo];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(heavyStuff) object:nil];
[operationQueue addOperation:operation];
[operation release];
[self myBar];
...
[operationQueue waitUntilAllOperationsAreFinished]; //if you need to block until operations are finished
At a lower level, you can use use -[NSThread detachNewThreadSelector:#selector(heavyStuff) toTarget:self withObject:nil].
If you're targeting Snow Leopard exclusively, you can use Grand Central Dispatch:
[self myFoo];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self heavyStuff];
dispatch_async(dispatch_get_main_queue(), ^{
[self myBar];
});
});
But it won't run on earlier systems (or the iPhone) and is probably overkill.
EDIT: it works on iPhone since iOS 4.x.
You've got lots of great pointers here, but don't forget to spend some time with the Threading Programming Guide. It provides good guidance not only on the technologies, but also good design of concurrent processing, and how to make better use of the run loop both with threads and instead of threads.
You could use NSOperationQueue and NSInvocationOperation:
[self myFoo];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(heavyStuff) object:nil];
[operationQueue addOperation:operation];
[self myBar];