So far my apps have been pretty simple, but now I'm finding I need to run a process on a separate thread, so this is an xCode 101 question asking how I do that.
I want to run a process that runs when the app launches, so I want to execute it in AppDelegate.applicationDidFinishLaunching.
From what I've read, I think this is all I need to do, but please correct me if I'm wrong.
// *** AppDelegate.m ****
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[NSThread detachNewThreadSelector:#selector([XMLParser parseXML:])
toTarget:self
withObject:requestStr];
}
// *** XMLParser.m ***
-(void)parseXML {
// Dunno why NSAutoreleasePool is needed but apparently it is
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// . . . my code
[pool release];
}
}
There is some problem i think, #selector expects a selector not a method call. So the correct one should be like this
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[NSThread detachNewThreadSelector:#selector(parseXML:)
toTarget:objXMLParser
withObject:requestStr];
}
//here the taget is the object whose selector you are passing. so you can't use self there as parseXML: is the method of XMLParser class
// *** XMLParser.m ***
-(void)parseXML {
// Dunno why NSAutoreleasePool is needed but apparently it is
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// . . . my code
[pool release];
}
// Autorelease pool is needed as it is a separate thread, and your code might use some cocoa or your own calls/methods/code that autoreleases an object that's why you have to keep an auto release pool for those autoreleased objects. if your code doesn't use any [obj autorelease] statement or doesn't auto release an object in that case you can omit auto release pool statements, but it's a good practice to keep it.
I have not used the method you describe, but have used NSOpertaions. It supports but concurrent and non-concurrent operations and is easy to use.
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.
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've this bit of code:
- (IBAction)registerAction:(id)sender {
[NSThread detachNewThreadSelector:#selector(registerThread) toTarget:self withObject:nil];
}
- (void)registerThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyDelegate *delegate = (MyDelegate *)[[UIApplication sharedApplication] delegate];
NSInteger locationID = [delegate.api checkCoordinate:[NSString stringWithFormat:#"%f,%f",
location.coordinate.latitude, location.coordinate.longitude]];
NSNumber *status = [api registerWithUsername:usernameField.text
password:passwordField.text email:emailField.text andLocation:locationID];
[self performSelectorOnMainThread:#selector(registrationDoneWithStatus:) withObject:[NSNumber numberWithInt:1] waitUntilDone:NO];
[pool release];
}
it works nicely, but sometimes I get this error:
void _WebThreadLockFromAnyThread(bool), 0x6157e30: Obtaining the web lock from a thread other than the main thread or the web thread. UIKit should not be called from a secondary thread.
And it seems that only using the delegate I get this error, and I don't know how to resolve.
Thanks in advance :)
I've run into the same problem recently.
There may be some active views (eg. UITextField,UITextView). Try resignFirstResponder those views before accessing delegate
You fix the problem by very carefully thinking through your application's concurrency architecture and ensuring that you aren't exercising anything from a thread that should only be done on the main thread.
In this case, you are causing the UIKit to execute code from a secondary thread. If you were to set a breakpoint on _WebThreadLockFromAnyThread, you would know exactly where.
It is exceedingly atypical to use the app's delegate from a secondary thread in anything but the most extremely controlled circumstances.
tl;dr You can't make an app threaded by detaching a new thread against a random selector.
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.
In my iPhone app I need to connect to a web server as this can take some time I'm using threads like this:
[NSThread detachNewThreadSelector:#selector(sendStuff) toTarget:self withObject:nil];
- (void)sendStuff {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Need to get the string from the textField to send to server
NSString *myString = self.textField.text;
//Do some stuff here, connect to web server etc..
[pool release];
}
On the row where I use self.textField I get a warning in console saying:
void _WebThreadLockFromAnyThread(bool), 0x5d306b0: Obtaining the web lock from a thread other than the main thread or the web thread. UIKit should not be called from a secondary thread.
How can I use the textField without getting this error?
It depends a little bit on what you want to do with the textField. If reading the value is the only thing, you can do:
[NSThread detachNewThreadSelector:#selector(sendStuff) toTarget:self withObject:self.textField.text];
- (void)sendStuff:(NSString*)myString {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Do someting with myString
[pool release];
}
If you want to change a value on the textField you could:
[self.textField performSelectorOnMainThread:#selector(setText:) withObject:#"new Text"];
Perform any selectors that handle UI updates on the main thread. You can do this with the NSObject method -performSelectorOnMainThread:withObject:waitUntilDone:
Why not:
[NSThread detachNewThreadSelector: #selector(sendStuff:) toTarget: self withObject: self.textField.text];
?
This is indeed unsafe behaviour. The MainThread is the only one that should interface the UI. Have your thread return for instance a string to the mainthread and have a method there update the UI. You can do for instance do this by passing a selector to the other thread method, and then have the other thread call the selector on the mainthread.
[self performSelectorOnMainThread:#selector(myMethod) withObject:nil waitUntilDone:NO];
will work