I have a window that displays some data in an NSTableView. This data is loaded in the background. The data-loading-thread is started in the windowDidLoad: method. If the window is closed before loading has finished, the background thread should be cancelled. I do this by signalling the thread in the windowWillClose: delegate method and waiting for the background thread to finish.
Now this all works perfectly. But I have one problem: How can I update the data in the table view? I have tried calling reloadData via performSelectorOnMainThread: but this leads to a race condition: The reloadData call is sometimes queued on the main thread after the window close command, and will execute after the window has closed, and everything goes up in flames.
What's the best way to control and communicate with a background thread?
Well, you know, this is exactly what makes the use of threading complex: you always face synchronization issues.
What I suggest is, instead of calling [tableView reloadData] from your thread, simply signal your controller (by calling a method controllerShouldReloadTable) and let your controller do the check if windowWillClose has been called or not. There might be a chance that your controller has been also released by the time controllerShouldReloadTable, and to fix this you will definitely need to retain the controller from the secondary thread.
On a side note, I would cancel the thread in viewDidUnload (for symmetry).
Most important: I would use asynchronous calls and a delegate class so that the whole multithreading issue is solved at its root.
EDIT: Sending asynchronously a request will not block the sending thread waiting for the response. Instead, asynchronous send (for NSURLConnection is called start) immediately returns (so, no blocking) and when the response is received, a delegate method will be called (i.e., connectionDidFinishLoading:) so that you can updated the model and the UI. Take a look at NSURLConnection docs, but as usual, I strongly suggest using [ASIHTTPRequest][2], which has many advantages.
Related
I have a worker thread that I keep alive through a loop that's controlled by a flag. I need the thread to stay alive for the length of my application as it opens a permanent connection to a remote server.
I fire up that thread and call several methods on it with:
[worker performSelector:#selector(getBusy) onThread:worker withObject:nil waitUntilDone:NO];
This seems to work fine and the method is called. At some point in getBusy I try to call a method in the main thread with:
[delegate performSelectorOnMainThread:#selector(gotBusy) withObject:nil waitUntilDone:NO
where delegate is a reference to the class that starts the separate thread.
The problem is that gotBusy never gets called on the main thread. I've peppered it with NSLog() statements and I can't see them printed on the console.
What should I be looking for to debug this?
First, make sure delegate is not nil. Secondly, make sure your main event loop is not blocked and not running in a modal mode.
Is it possible that you never assigned delegate, so you're calling performSelectorOnMainThread on a nil object? You could try setting waitUntilDone to YES so your worker thread will block and let the delegate do its work.
I am not clear about what does waitUntilDone do, but I found this thread:
What is the significance of WaitUntilDOne in performSelectorOnMainThread?
which makes me a bit clear, however, if I perform some selector which makes NSURLConnection(which is asyncrhonous) and waitUntilDone set to YES, what will happen then? It will wait for the method to execute, but the method actually does some asynchronous operation(ie NSURLConnection), then what is the impact?
Thanks!
NSURLConnection is asynchronous. Your code runs on the main thread, and it makes delegate calls to you as the download progresses. You don't need to, and should not, run an NSURLConnection from a background thread.
If you DO have code that needs to run on a background thread, you can use the preformSelectorOnMainThread method to send messages from your worker thread to the main thread. One common reason to do this is that you can't update the UI from a background thread. You'd invoke a method to update the UI on the main thread.
The flag waitUntilDone controls what happens after the performSelectorOnMainThread call. If waitUntilDone is false, your background thread continues on with the next line without waiting for the code on the main thread to finish.
If waitUntilDone is true, your background thread will block until the main thread finishes performing the selector that you sent it.
I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
I am using the NSNotificationCenter to notify the class containing the loop when the button is pressed.
However, If I press the button while the loop is being executed, the notification occurs after the loop is complete instead of interrupting the loop.
I'm guessing this is because it is operating in the same thread.
So how do I get the NSNotificationCenter operating in a background / different thread? Is this possible? Or is there a better way to do it?
It's not just the notification center.
I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
The events for that button press come in on the main thread. If your loop is running on the main thread, then the button press itself does not get processed until your loop is finished. The notification is posted immediately, relative to the button press actually getting processed by your application.
Or, in list form:
The user presses the button.
Your loop runs out of things to do and returns.
The button press arrives in your application and is turned by the button into an action message.
You post the notification.
You receive the notification.
The delay that you're seeing is between steps 1 and 2; step 4 happens immediately after step 3.
Notifications on a local (not distributed) NSNotificationCenter are dispatched on the thread you post them from, so posting it from your action method means that it will be dispatched on the main thread. This is normal and OK.
Move the loop, not the notification, to a background thread, dispatch queue, or operation queue. If you use an operation queue, you may not need the notification at all, as you can tell an operation queue to cancel all pending operations. (Your operations will need to check at any appropriate time(s) whether they have been canceled; for reasons previously discussed, killing a thread/operation at a random time is a Bad Idea.)
Background threads, blocks, and operations can communicate back to the main thread when needed (e.g., to update the UI). To send a message through the main thread's run loop, use performSelectorOnMainThread:withObject:waitUntilDone:. To dispatch a block on the main thread, use dispatch_async and dispatch_get_main_queue. To schedule an operation on the main thread, add it to [NSOperationQueue mainQueue].
For more info, read the Concurrency Programming Guide and the Notification Programming Topics.
I would run your loop in a separate thread, and have an instance variable BOOL abort;, when your button press notification comes in, set abort = TRUE; then in the loop check this value and exit if it is true.
I would run the loop in a separate thread. Even better, make it an NSOperation so that you can call [.. cancel]. Just make sure to use performSelectorOnMainThread when updating the UI from the NSOperation object. It's not a good idea to have a long running loop on the main thread.
You can't put the notification center on another thread. That object is out of your control. The problem isn't so much that they are on the same thread as that you are not allowing the run loop, which is responsible for handling the button press, to do anything. (There's one and only one run loop per thread.) As has been stated by both edsko and Peter Hosey, the button press itself, and in fact your entire UI, is stopped while your loop is running. It is generally a good idea to put long-running operations onto a background thread, then call back to the main thread to update the UI, performSelectorOnMainThread:withObject:waitUntilDone: being an easy way to do such a call back.
That said, if you were to keep the loop on the main thread, you need to let control return to the run loop periodically so that the button press will be registered. There are two ways I can think of to do this. First, you can explicitly give the run loop control briefly during each iteration of your loop:
while( !buttonWasPressed ){
// Do work...
// Let the run loop do some processing before the next iteration.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
Or, you can make a single method that consists only of the code from your loop, and use performSelector:withObject:afterDelay: to have the method repeatedly called while still allowing the run loop to work:
- (void) loopTheLoop {
if( buttonWasPressed ) return;
// Do work...
// Run this method again as soon as possible.
[self performSelector:#selector(loopTheLoop)
withObject:nil
afterDelay:0.0];
}
I want to make sure that all my initializations for my views and stuff are handled every time my application starts, even when it is called back after being sent to the background, such as with multitasking.
What's the best way to do this? should i use applicationDidBecomeActive to call viewDidLoad on my viewcontroller directly? I'm guessing this is not wise. I just want to make sure that stuff gets done on load every time the user calls up the app, no matter what state it is in at the time.
I have several apps published that do just that - call viewDidLoad on one or several UIViewControllers from applicationDidBecomeActive.
It works just fine. Apple doesn't object to it either.
However, be aware that if you have allocations in your viewDidLoad you need to either check for already allocated instances or release the instances you allocate in viewDidLoad when your app suspends. The same goes for DB connections that need to be closed, notification listeners, and so on.
As long as you watch for these elements and handle them correctly, the approach is valid and very usable.
I'm fairly new to Objective-C and I can't figure out how to wait in a non-blocking manner. I have an object that is being populated asynchronously and I need to wait on it before I can proceed in another method. Right now I am using the sleep function, but this blocks the whole app and myObject never gets loaded.
while (!myObject)
{
sleep(1);
}
return myObject;
EDIT: This code snippet is from a method that may be called before myObject has been loaded. In this case I actually do want to block in this method, but my code blocks everything including myObject from being loaded.
This little peach worked for me (in-order to delay for 20 seconds)....
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20.0, false);
If you can, give the class a myObjectLoaded: method to be called when the object in question is loaded. Otherwise, the the most idiomatic equivalent of what you wrote above is to create a timer that keeps checking for myObject and does something once it finds it.
If you really needed to do it in the middle of a method for some reason, you'd have to create a loop that keeps running the runloop. It's the lack of a runloop that causes your app to block.
NSNotification should solve the problem.
http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
Instead of waiting on on object, have this object register for notifications coming from your other object (say Publisher) which populates the data asynchronously. One this Publisher object finishes, have it post an NSNotification, which will then be automatically picked up by your waiting object. This would eliminate waiting too.
Sounds like you're looking for the observer pattern. Apple calls it "notification".
Assuming you have some background NSThread performing this population operation, you might like The NSObject method performSelector:onThread:withObject:waitUntilDone
That's because you are stopping the main thread waiting for your object to be loaded. Don't do that, because the main thread is the thread that drives the UI, and waits for user input. If you block the main thread you block the application user interface.
If you want the main thread to do something when the object is loaded, then create a method myObjectLoaded: and call from your loading threads:
[myObjectController performSelectorOnMainThread:#selector(myObjectLoaded:)
withObject:myObject
waitUntilDone:NO];
where myObjectController can be any object even myObject itself.