I every time use dealloc for remove observer, but just faced with this link that describes that we can use viewWillDisapper instead of dealloc.
The choice is a matter of properly pairing the addObserver/removeObserver calls.
If you call addObserver in a place that is called once such as some init method or viewDidLoad then call removeObserver in dealloc.
If you call addObserver in a place that is called multiple times like viewWillAppear then call removeObserver in viewWillDisappear.
It's the proper pairing that matters.
One critical thing about KVO is that you MUST match removeObserve and addObserver calls and you cannot add duplicate observers. This means that you must carefully think through where you add the observer and where you remove it so that you don't violate either of those restrictions.
If you add it in viewDidLoad it is currently sufficient to remove it in dealloc (since viewDidUnload isn't used any more), but the observer may trigger when the view isn't visible. If you're running on older OS'es where viewDidUnload is still called, this can also be problematic as you have to track when the observers are in place and when they aren't.
You can add it in viewDid/WillAppear, in which case you need to remove it in viewDid/WillDisappear. This is usually cleaner since the calls are (generally speaking) guaranteed to be matched up.
The link you reference is relatively informative and accurate.
I think of it in these terms:
a) does the VC need to be notified while it's on screen only? use viewWillAppear/viewWillDisappear.
b)does the VC need to be notified as long as it's alive (but not necessarily on screen)? use init or viewDidLoad and remove in dealloc.
I've breakpointed my dealloc methods under ARC and they're called when expected. However viewDidUnload is not called.
Related
The [UIViewController viewDidLoad] method is called by the system after loading the associated view (obviously?). A common belief -- which I share -- is that viewDidLoad should not be called directly †. However, I can't find this guidance in the documentation, nor anything else from Apple. Does it exist?
For comparison, the loadView documentation says
You should never call this method directly.
† Excepting [super viewDidLoad] in an overridden method.
I don't think there's any rule that forbids it. I don't mean "there's a rule we all know, it's just not written down." I mean "I don't think there's any rule that forbids it." You are, as best I am aware, free to call viewDidLoad whenever appropriate (including its super).
That doesn't mean you should call it. But then you almost certainly should never call OSCompareAndSwap either. In both cases, if you had a good reason and knew what you were doing, it could be appropriate to call. But it's unlikely to come up.
While I can't think of a time I've had to call viewDidLoad directly, I have had to manually call viewWillAppear and viewDidDisappear to manage view lifecycle in a custom container view controller. There's nothing that forbids calling view lifecycle methods if that's what you mean.
That said, it would be bad practice to call a view lifecycle method if you didn't mean "the view has transitioned into this state." And since it's pretty hard to get into a situation where you have loaded the view, but viewDidLoad won't be called, it's hard to imagine many cases where it would be useful. And you shouldn't call a method uselessly. So that's the only rule at play here as far as I'm aware.
It is not mentioned explicitly but if you would call it, you would somehow violate the "contract" of the method specified in its documentation:
Called after the controller's view is loaded into memory.
I am wondering if it is a good practice to implement a deinit on every view controller to check if it is correctly removed when it disappears and avoiding leaking memory?
By default, you don't have to implement the deinit method in your classes:
Swift automatically deallocates your instances when they are no longer
needed, to free up resources. Swift handles the memory management of
instances through automatic reference counting (ARC), as described in
Automatic Reference Counting. Typically you don’t need to perform
manual cleanup when your instances are deallocated. However, when you
are working with your own resources, you might need to perform some
additional cleanup yourself. For example, if you create a custom class
to open a file and write some data to it, you might need to close the
file before the class instance is deallocated.
Swift Deinitialization Documentation - How Deinitialization Works Section.
Usually, when working with View Controllers it seems that there is no need to do such an implementation. However, as mentioned in #rmaddy's comment, it is still an approach for tracing memory leak or reference cycle with the view controller.
If your purpose is to check if the controller has been removed from the hierarchy (view controller life cycle), you could implement viewWillDisappear(_:) or viewDidDisappear(_:) methods; Note that calling on of these methods does not guarantees that the deinit will be called, i.e it does not mean that disappearing the view controller always leads to deallocate it (related: Deinit never called, explanation for deinit not called).
Also:
these Q&As should be useful:
When should I use deinit?
how to make deinit take effect in swift
Understand deinitialization and inheritance in swift language
Swift automatically deallocates your instances when they are no longer needed, to free up resources. So to add deinit on all your viewControllers seems unnecessary. You should call deinit whenever you need to do some action or cleanup before deallocating an object.
Well during the tests phase it maybe good idea, because you can check if everything is good (eg. if you have a lot of completion handler) but overall it is unnecessary.
I have a view contoller (for log in) that defines deinit method to remove observers. When user taps on Register button, the controller calls performSegueWithIdentifier to load registration view controller. I defined deinit method to remove all observers. However, that method is not being called. I read it somewhere that it is because the viewcontroller is not being destroyed and the pointer to it is hold somewhere. Could anyone explain the reason behind this?
Thanks
EDIT : Although I agree that the link provided in the comment section holds the same question, there is no answer or clear answer to that. The highest upvoted reply suggested to define something inside deinit instead of leaving it blank. That does not answer the explination I'm looking for. For that reason, I'm keeping this question until somebody can point out my understanding is incorrect.And also, I do think that Matt explains it succinctly and clearly.
The usual reason for failure to trigger deinit when expected is that you have a retain cycle that prevents your view controller from going out of existence.
(Sometimes the reason is that your expectation that the view controller would be destroyed under the circumstances is incorrect. But assuming it is correct, a retain cycle is the reason.)
You mentioned removing all observers. What kind of observers? If we're talking about NSNotification, that is often how you get a retain cycle. The notification center retains the observer until you unregister it. You thus cannot get deinit until after the observer has been removed. Therefore, you cannot remove the observer in deinit.
I am using a UITabBarController, and my 3rd tab observes an array on a singleton data store (implemented in viewDidLoad).
Currently if I just log out (and change root view controller from App Delegate), the app will crash when dealloc is called on that 3rd tab with the message "cannot remove observer for the key path "X" because it is not registered as an observer.
Using breakpoints, I see that viewDidLoad is never called on this 3rd tab, however dealloc is being called when I sign out. What is going on? I assume the UITabBarController is holding a reference to the 3rd tab when I enter the storyboard, but does not "load" that tab. Yet iOS calls dealloc on it when I release the tab bar controller.
Should I use a boolean to track viewDidLoad execution, or try to remove the observer with a #try statement? Is there an overall better design for this?
Do not use #try. Exceptions in Objective-C should always be considered programmer error, and should be fatal.
As you say, use a boolean ivar set in -viewDidLoad to avoid this.
The view has not been loaded because views are only loaded when they are required for display.
Raw KVO can be dangerous and unwieldy. While not required to answer this question, ReactiveCocoa significantly improves the KVO experience.
viewDidLoad is called before the view appears for the first time. UITabBarController is creating the relevant UIViewController, but the view is not loaded during creation. It is loaded on-demand, when a user visits the tab for the first time.
KVO removal is problematic, I don't think you can avoid using #try in dealloc. I would suggest to use KVOController: it's fairly easy to use and it would also handle all the edge cases for you.
May have found an even better solution. I add the observer in the method initWithCoder:(NSCoder *)aDecoder, which is called when the parent UITabController is loaded. I am using the storyboard which may be why I need to call override this method instead of regular init. Doing this now without the need for a BOOL flag or #try and no crashing.
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[anObject addObserver:self forKeyPath:aKeyPath options:0 context:NULL];
}
return self;
}
Use a flag to set whether or not KVO has been set up. Using #try can create memory management issues depending on the state of the app.
I'm using ARC (automatic reference counting).
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead of viewDidUnload?
Such as these:
[self setTheImage:nil];
[self setBTNplay:nil];
[self setBTNstop:nil];
I'm writing a navigation based app which includes pageViewController, I tested my app in Instruments to see the memory leaks and I keep getting receive memory warning message.
I've even put a log code in the viewDidUnload method. But it doesn't seem to get called when I even pop to rootViewController!
One more thing: If each page has an audioPlayer, where should I set a #property (nonatomic, strong) AVAudioPlayer *audioPlayer; to nil?
Or how do I set it to weak instead of strong? Because it gives me a 'warning' then in this code line:
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:poemURL error:nil];
it says: Assigning retained object to weak variable
You don't need to nil out those values in viewDidUnload. Make sure you're using weak properties instead of strong or assign for IBOutlets. Received memory warning doesn't necessarily mean you're leaking. Received memory warning means that your app is consuming too much memory. Run Instruments and edit your question with how much memory your app uses.
The fact that you're using AVAudioPlayer makes me thing that maybe you're pulling down some massive audio files into memory.
Also by the way, initWithContentsOfURL:error: will get you rejected from the App Store because you're blocking the main thread. Try testing your app on an iPhone with only cellular enabled and go into a part of your office/house that has a bad internet connection. Also try with your phone switched to airplane mode. Your app will undoubtedly either freeze for a long time before the connection fails or it will simply crash.
Instead, you should be using grand central dispatch or downloading it via NSURLConnection's block or delegate methods.
First, do not set to nil your properties in viewDidDisappear cause your view is still loaded. You must always set them to nil in viewDidUnload. It's invoked in low memory situations and here you must clean-up all stuff that breaks system memory.
Apple's UIViewController reference for viewDidUnload
When a low-memory condition occurs and the current view controller’s
views are not needed, the system may opt to remove those views from
memory. This method is called after the view controller’s view has
been released and is your chance to perform any final cleanup.
Second , take a look at this tutorial where is explained very well ARC
Are you calling [[NSNotificationCenter defaultCenter] removeObserver:self]; from one of your view controller subclasses? If so, that would explain why you're not getting viewDidUnload called.
If that's the problem, you should remove yourself from specific notifications when needed rather than all notifications as above. (It's OK to call removeObserver:self from dealloc, though.)
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead
of viewDidUnload?
There are many things wrong from this statement.
First of all, you do not set IBOutlets to nil in viewDidDisappear. viewDidDisappear is called when the view "disappears" (e.g. when it's in a tab bar controller, and you switch to another tab; or it's on a navigation controller, and you push something on top of it); the view can then "appear" again without loading again. If you set IBOutlets to nil, they will not be set again when you appear. (They are only set when the view is loaded.)
Second, if you have a leak, and setting stuff to nil "fixes it", that means you are not releasing the instance variables. You must always release retained instance variables in dealloc.
I've even put a log code in the viewDidUnload method. But it doesn't
seem to get called when I even pop to rootViewController!
Yes, viewDidUnload is only called in low memory situations. It is not called normally in most situations. If you were depending for it to be called, you were using the wrong method.