I have a view controller, which calls performSelector:withObject:afterDelay. However, if I remove that view controller right after calling this, my app crashes as soon as the system tries to perform the delayed selector on that (deleted) view controller.
Now how can I go about this? I need to get rid of the view controller to save memory, so there's no way to let it hang around.
Any way to cancel a delayed perform selector before it performs?
I suggest to use an NSTimer instead. You can simply invalidate the timer to make sure it will never be called after the UIViewController has gone away. A good moment to invalidate the timer is for example in viewWillDisappear:.
This does mean that the timer is owned by the view controller. But that is a good design anyway.
You can't perform a selector on a deleted object, you either need to have the object around, or do the work with some other smaller object that you can have hanging around.
To cancel there is a cancelPreviousPerformRequestsWithTarget:selector:object: or cancelPreviousPerformRequestsWithTarget: method.
Related
I'm fairly new to Cocoa Touch. Right now I'm trying to subclass UIViewController to provide my custom view. Since I intend to save the content of a UITextField (passcodeField) using NSUserDefaults, I want to be notified whenever the UITextField changes its value.
I've read somewhere that in order to do that I should add the view controller to be an observer of the UITextFieldTextDidChangeNotification notification. However I'm just not sure when to do that. I've considered several options.
In the -loadView method. However, since I'm loading my view using a XIB, I think i shouldn't mess with this method and should instead leave it as-is. (Am I correct on this point, BTW? )
In the -viewWillAppear method. But this method may be called multiple times because the view may be moved out and into the screen without being destroyed and recreated. (Am I correct? ) This will not do any harm to the program but sure doesn't seem like the correct way.
In the initializer of the UIViewController. If I want to add the notification there I must reference the UITextField. By doing this I essentially cause the view to created before it is really needed. Also I think I read somewhere that if the system runs low on memory the offscreen views may be destroyed. Thus I may lose the notification observing if such thing happens, right?
So I'm totally confused right now. Could you guys give me some advice of where to put it? Thanks so much!
Put it in the - (void)viewDidLoad method of your ViewController remember to call [super viewDidLoad]; at the start of your implementation.
Most of the iOS apps I use are very responsive, when I tap on an element it goes to the next view right away. In my app, some of my view controllers take 0.5-1.0 second to load.
My code is all in the viewDidLoad method and I'm pretty sure that's the problem but I can't move anything out since I need every single element that I instantiate.
A solution I thought is to move all the work I do in viewDidLoad in a thread then call the main thread when I'm ready to call addSubview, would that work even if UIKit is not thread safe? Or is there something else I'm missing?
Try to move some code you might have in viewDidLoad to viewdidAppear. viewDidAppear is being called once the view is presented. If you have to make some hard work, do it there and maybe show aa spinner somewhere while you do that.
What are you exactly doing in viewDidLoad? Btw remember that a view is only loaded when you need it, if you want to switch between views faster I can suggest you to create an initializion phase where you call -view on all the view controller you want to show, maybe helped with a spinner or a progress bar. but pay attention this would work only with intensive loading task and not memory consuming tasks. It sounds very strange your request, so is better the you try to explain better why your viewDidLoad is so slow, maybe there is something wrong.
Define your UI elements in Xcode as part of designing the interface. That way, Xcode can compile your storeyboard or xib files into the rapidly loading binary form.
I have 2 viewControllers in my app, from first view when i navigate to next one there is a button named startTimer with a timer action as selector method on Click of startTimer the timer starts up in HH:mm:ss format, i am not invalidating timer,but When i go back to 1st viewController and again if i come to again 2nd viewController and if i press startTimer button the timer again starts from 0, but i want it to be retained the previous value,how can i achieve this? i know that since i'm loading again the viewController the nib will be loaded freshly to memory but how can i retain the timer label and timer value?
Any help is appreciated in advance.thank you.
You've broken MVC (Model-View-Controller) by putting your data into your view controller. Moreover, you're asking a mechanism with no solid promises about time (NSTimer) to keep track of time for you. NSTimer does not fire exactly at the interval you request. It can fire at any arbitrary point after that interval. Using NSTimer as a stopwatch will almost always lose time (sometimes quite a lot of time, particularly if there's a scrollview around). (That last bit is overstated. A repeating timer schedules itself correctly so won't usually lose time. You'll just lose time if a repeat is completely skipped, which can happen during long scrolls or other things that can keep timers for firing for a full second.)
Create a new model object to hold the stopwatch information. Let's call it Stopwatch. Assuming you need it to be startable and stoppable, it needs an NSTimeInterval accumulatedTime property and an NSDate lastStarted property (you could also make lastStarted an NSTimeInterval if you like). So to start the stopwatch, you set lastStarted to "now." To stop the stopwatch, you clear lastStarted and move the current accumulated time to accumulatedTime. To find out the current time, you add accumulatedTime to now - lastStarted.
OK, now that you have that, what can you do with it? You can pass it to your view controllers and they can ask "what's the current stopwatch value?" They can start and stop it as they like.
Now your view controller would like to update its display every second, so you have a timer that does that. Every second it asks the stopwatch, "what's the current time" and it displays it. But it does not set the time. It just asks.
BTW, you can also use KVO on Stopwatch, but it's a little trickier, since Stopwatch needs to run its own timer to send out the change notifications. I generally find this more trouble than its worth.
Don't do like that, this way every time you load the view containing the NSTimer, a new NSTimer object is created and the old one is still in the memory since you're not invalidating it.
The best way is that you must put NSTimer in the Application Delegate and then start it only when you first time load that View Controller.
For achieving this, you must put a flag to check that the View is loaded first time or not.
If the NSTimer is one of your instance variable, I guess you could do a check to see if it is allocated or not.
//NSTimer *timer; declare this in your interface
if (timer==nil)
{
// allocate timer
}
//Do nothing if it is allocated all ready
What I would do is to mantain the second view controller as an ivar of your first view controller. This way, you can instantiate it just once and your NSTimer will remain in memory.
However, if you want to maintain your style, you should save the current time in any kind of preferences (look at [NSUserDefaults standardUserDefaults] or create your own singleton class). Then in view did load method from your second view controller, load that value and add it as an offset for your timer.
Hope to help!
I subclass MPMoviePlayerController. In that class I attached all possible notifications that I need. DidFinishPlayback, ExitFullScreen etc. in it's loading method. My question is, if I want to STOP movie and dismiss movie player view can I (and do I need to) remove observers in moviePlayerPlaybackStateDidChange method on stateStopped? What can happen if I don't do that?
The most important place to remove an observer of any kind is in the dealloc method. It is best practice to remove the observers as soon as your done observing but absolutely needs to be done by dealloc.
The reason for this is because if you register as an observer for something and your class gets deallocated the object you were observing could possible try and callback to the now deallocated object. More than likely this will cause an EXC_BAD_ACCESS and close your application.
One of the tabs of my UITabBarController takes some time to work before it can be displayed.
What is the best way to display a "Now Loading" before the viewcontroler completes its work?
I tried setting up a "now loading" view in the tab's viewController's viewDidLoad method, then I do the work in viewDidAppear, setting a flag to not do the work again on next time through viewDidAppear.
However, I never see the "now loading" view... some optimizing must be getting done -- the viewcontroller's viewDidAppear is called before the TabBarControllerDelegate didSelectViewController.
Is there a UITabBarController mechanism that would allow for a placeholder view to be displayed before the viewcontroller is displayed?
Any ideas?
Thank you-
Matt
I could be wrong, but perhaps your problem is that by doing the time-consuming work in viewDidAppear, you're blocking the main event thread so that the view doesn't update until the work is complete. I.e. you set up the "now loading" in viewWillAppear, but you never see it since, by the time viewDidAppear completes, it's done with the heavy work.
NSObject's performSelector:withObject:afterDelay: method can be useful here. Display your "Please wait" alert or view, or whatever, then use performSelector:withObject:afterDelay: to start the actual work. Your loading will be delayed until after the next execution of the event loop, by which time the user interface will have been redrawn.
The technique to use here is this:
put up a "Loading view" in your
controller's viewWillAppear: or
viewDidLoad: method
then, spawn a new thread to do the
actual loading (or whatever time
consuming process you're doing)
when complete, send a message to the
controller (using the delegate
pattern, for example) that the
"loading" is done
finally, remove the Loading view and
let the user proceed
Doing it this way leaves your application interface still usable, even though the particular view controller is busy.
There are no build in methods to do this, you'll have to code it all yourself.