I have a main and an auxiliary thread in my app. Main thread as everyone knows is being used by UI. I use the secondary thread to do the background loading of my views.
I have a main controller which i call a dummy controller. From there i call my main controller, which is in landscape mode.
So basically what i have to do is, when i rotate my dummy controller, i want to be able to load my main landscape controller and vice versa.
Everything works fine when i rotate from dummy to main.
The main has a series of child controllers to load, so i have put up that process on a secondary thread, and by that time they are loading i show a loader screen.
The dummy is a normal view controller, and the main is a modal view controller.
Now the problem.
if a user is trying to rotate form dummy --> main, the rotate method kicks of the initialisation of the main view controller. But for some reason the user changes his mind and rotates back form main-->dummy ideally, the transition should be smooth as the UI thread(main theread) is free. What happens is the UI rotates back. But since the secondary thread on the Main controller is still on, even after the dismiss modal controller is called on the MainController by the main thread, when we call the willRotate function.
I've read in various post that we cannot simply kill the secondary thread, as we have to do clean up stuff ourselves. So can anyone please guide me through code, how do i kill my secondary thread and do clean up before i transit back to my calling form..??
Its kinda urgent... please dont point to other thread topics, cos i've already visited those.
I specifically want to know how should i stop my secondary thread, without causing a crash.
thanks.
-(void)viewDidLoad {
[super viewDidLoad];
workingThread = [[NSThread alloc]initWithTarget:self selector:#selector(parseMainTextFile) object:nil];
[workingThread start];
[NSThread detachNewThreadSelector:#selector(addLoader) toTarget:self withObject:nil];
//[self performSelectorOnMainThread:#selector(parseMainMatchTextFile) withObject:nil waitUntilDone:NO];
}
Generally saying, if you run a background thread it means you've got a Run Loop, which works in a while statement. You should have a flag which stops the Run Loop. this way it would transform the state of the thread to finished.
while (mRunLoop && [mLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
This is the way I do. Additionally read this Post and its sub posts from comments. Good luck!
Related
I'm trying to understand how things work in regards to concurrent programming and calling setNeedsDisplay. I basically have Three objects.
Main View - container with different UIView objects, the main one being a UIScrollView
Small Map View - a small UIView that draws a miniature version of one of the other UIView items on screem
Processor - a delegate of the Main View that calculates what's on screen and calls the Main View back with what's in view.
So a simple use case of what's going on is the user touches the ScrollView and then the processor updates what's in view of the scrollView (like calculating coordinates, center point, etc) It does this using blocks and does it asynchronously. This then posts a notification to the MainView object.
When the MainView receives the notification, it just calls
[smallMap setNeedsDisplay]; // example 1
I put some logs around this call, and I do see it gets called right away. However, the drawRect: of this function does not get called right away. It gets called after 2 seconds or so.
I remember reading that setNeedsDisplay just marks the view for redraw to happen on the next event of the run loop.
But if I add this code instead:
// example 2
dispatch_async(dispatch_get_main_queue(), ^{
[smallMap setNeedsDisplay];
});
My view gets redrawn right away.
I guess I'm confused as to why I have to ask for the main event loop to call setNeedsDisplay to immediately redraw something. Like in example 1, by me calling setNeedsDisplay, is that done in the background or something and that's why it doesn't get redrawn right away? I'm trying to understand the difference in what's going on behind the scenes so I know what to look for the in future. Like should I have all my calls that need to be immediately redrawn in something similar to the example 2 block? Or is it because I'm processing my data asynchronously that I need to then ask for the main queue? Thanks!
My guess is 1 of 2 things:
Your code that is running on a separate thread is calling your MainView methods from the separate thread instead of using performSelectorOnMainThread or a GCD call that invokes the code on the main thread. Thus your call to setNeedsDisplay is actually taking place on a background thread, which is a no-no, as the other poster said.
The second possibility is that your MainView code is running on the main thread, but it gets busy doing time-consuming processing, or waiting for a synchronous call to another thread to finish, and doesn't service the event loop.
You can rule out the first possibility by setting a breakpoint on your call to setNeedsDisplay and looking at the call trace in the debugger to see what thread it's running from.
Figuring out the second possibility will take a little more work. You might need to delve into instruments.
setNeedsDisplay is a UIKIT API call and has to be called from the main thread of the application, also known as the UI thread. That's why calling it in a background thread doesn't have any immediate effect and scheduling it on the main queue has immediate effects.
See this related question https://stackoverflow.com/a/6988115/172690 for a more detailed answer.
Is this code using UIActivityIndicatorView flawed? It appears that I don't actually get to see the indicator/spinner at all here, so is this because the view isn't drawn until the who viewDidLoad completes?
Is the only way around this to do the viewDidLoad custom work (e.g. data updates) on a separate thread? (I was hoping in this case for an easier single-thread operation). Is there a way to force the view to refresh after the "startAnimating" line perhaps prior to the data loading commencment?
Code from UITableViewController implementation:
- (void)viewDidLoad {
// Wait indicator - Start
self.waitView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
self.waitView.hidesWhenStopped = true;
[self.view addSubview: self.waitView];
// Load data into tableview
[NSThread sleepForTimeInterval: 5.0]; // Test code to simulate
[self.waitView stopAnimating];
}
You should also call startAnimating. Sleeping is not a good idea. I would prefer the performSelector-methods which starts a not recurring NSTimer under the hood.
Try this:
-(void) doStuff:(id)aSender
{
[self.waitView stopAnimating];
}
-(void)viewDidLoad
{
...
[self performSelector:#selector(doStuff:) withObject:self afterDelay:5.0];
}
in addtion: also set the frame- or bounds-property of the ActivityIndicatorView somewhere like sosborn said in his comment
Actually the answer from Thomas should work as it is, I will add a little explanation as to why not use sleep as you have done it.
All the UI processing on iPhone (and most of OSs as well) is being done in only one thread - the main thread, the thread that executes the so called run loop. If you stop that thread the UI will stop, nothing will be drawn.
Putting sleep into viewDidLoad, which runs in the main thread, will do just that - stop UI from doing anything. So because immediately after wakeup you've called [self.waitView stopAnimating] and the activityview should hide when not animating, you can't see it at all - you just didn't give it any time to show.
Thomas used a NSTimer to call stopAnimating after 5 seconds - now this lets the main thread to execute code before stopping animation and hiding waitView and this will work for your test.
Better yet you just let it animate without any timer and use a delegate patter to be informed by the tableView loading code after the data has been loaded, then stop animating. You don't know how long loading of data will last, so it's better to wait until it's finished than stop animating after any specific time.
Oh well, and the size and position, makes sense, but for testing it doesn't matter and is not the cause of not seeing it - if not specified it will be added at 0,0 and have a default size so you will see it anyway.
My understanding of Runloops is basic so this may seem like a very trite question.
I have the following in my application:didFinishLaunchingWithOptions (or applicationDidFinishLaunching):
{
// 1. typical app setup work: create various views, create a tab bar, add
// navigation controller and views to the tab bar
// 2. perform some other app initialization tasks
// 3. Add main view to the window
[window addSubview:tabbarController.view];
// 4. Make window visible
[window makeKeyAndVisible];
// 5. Perform one final behind the scene task
[myTaskObject doSomeTaskHere];
}
Do each of these methods get executed in order listed or is there any chance that step #5 can happen before the app's main runloop completes the work of putting up the main window with '[window makeKeyAndVisible]'
Does the doSomeTaskHere need to get wrapped up into a performSelectorOnMainThread:withObject:waitUntilDone:YES to ensure that the runloop completes the displaying of the window and thus loading whatever view that is the topmost view before 'doSomeTaskHere' is invoked?
Those tasks will execute in order on the main thread's run loop. Since UI updates also occur on the main thread you will not allow your app to redraw the screen until after you return from -application:didFinishLaunchingWithOptions: so while [window makeKeyAndVisible]; will complete before [myTaskObject doSomeTaskHere]; you are still blocking the UI from updating until that doSomeTaskHere is complete.
If doSomeTaskHere is an expensive operation you should schedule it for a future iteration of the run loop or better yet do that work on a different thread so that the UI can update and respond to touches.
performSelectorOnMainThread:withObject:waitUntilDone:YES would not allow the main thread to update the UI as unless you passed NO as the last parameter. Telling the main thread to wait until the main thread finishes some other work isn't very useful or any different than invoking that selector directly.
I have a UIView that I want to load when the user clicks a button. There happens to be some data processing that happens as well after I call addSubview that involves parsing an XML file retrieved from the web. The problem is the view doesn't show up until after the data processing even if addSuview is called first. I think I'm missing something here, can anyone help?
Code: I have a "Loading..." view I'm adding as a custom modal (meaning I'm not using the modalViewController). This action is linked to a button in the navigationController.
- (IBAction)parseXml:(id)sender {
LoadingModalViewController *loadingModal = [[LoadingModalViewController alloc] initWithNibName:#"LoadingModalViewController" bundle:nil];
[navigationController.view addSubview:loadingModal.view];
[xmlParser parse];
}
Howdy! If you're looking for an easy work around:
[self showLoadingScreen]
[self performSelector:#selector(methodToDoWork) withObject:nil afterDelay:0.3];
However you're better off making methodToDoWork asynchronous if you can.
If you are doing your processing on the main thread, it will block the main thread until its done, which means your UI will become unresponsive and not update until the main thread resumes.
You need to perform your XML processing on a background thread using something like NSOperation or an existing asynchronous API and update your view when you have finished.
Its hard to be of more help and get a better idea of whats going wrong without seeing your code unfortunately.
How do I create a loading screen that can be reused at any given time. I'm aware of the Default.png but I need the flexibility to plug in a loading screen at any point during the application life cycle.
This is what I have thus far.
//inside a method that gets called by a UIButton
LoadingViewController* loadController = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil vertical:NO];
[self.view addSubview: loadController.view];
//some method call that takes a few seconds to execute
[self doSomething];
//This loads some other view, my final view
[self.view addSubview: someOtherView]
but it seems that the loading view is never displayed. Instead the previous view stays there until the "someOtherView" gets added. I put trace logs and the code does seem to get executed, I even replaced [self doSomething] with a sleep(2), but the intermediate loading view is never displayed.
If I remove [self.view addSubview:someOtherView]; then after a few seconds...(after doSomething finishes executing) the load view is displayed since there is no view that is pushed on top of it, however this is obviously not the functionality I want.
Can explain this behavior? Is there something about the rendering cycle that I am misunderstanding because it doesn't seem like the view (on the screen at least) is instantly updated, even though I call a [self.view addSubview: loadController.view];
Would I need to create a separate thread?
In general, for changes in the UI to be made visible to the user, control must return to the main runLoop. You are only returning to the runLoop after taking the loading view down and replacing it with the other view. One strategy for dealing with this is to move the code that does the loading onto another thread. NSOperation and NSOperationQueue can be used for this.
An even simpler approach is to use performSelectorInBackground:withObject to do the processing. Once processing is complete the UI can be updated again to show the data. It is important to remember that the UI updates must be carried out on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: to accomplish this from the loading thread.
This sounds like a lot of complication but it is really as simple as breaking your single method up into three separate methods as follows:
Display the loading view and start the background process - this is the button action method.
Do the background loading - called from the button action function with performSelectorInBackground:withObject.
Remove the loading view and update the display with the data - called from the background thread with performSelectorOnMainThread:withObject:waitUntilDone.
I created a subclass of UIView where I initialized how my loading-view should work and look like. (My view appeared and slided in from the bottom with an nice animation).
I then added code that handled whether the loading-view should be visible or not in a subclass of UIViewController.
I then let all my viewcontrollers be an subclass of my new viewcontrollerclass which made it possible for me to do:
[self showloadingMessage:#"loading..."];
in all my viewcontrollers...