Observer/Dealloc errors and crash with unseen UIViewController - iphone

I have a UIViewController which has objects perform a series of tasks on background threads as it appears, and it's registered as an observer to them. When they get called for the first time, it stops being an observer.
I realised I could save user time if these tasks were performed before the view controller is shown, so I initialized it and called a method which ran these tasks.
Then I started getting errors along the lines of:
An instance [insance] of class [class name] was deallocated while key value observers were still registered with it.
How can I prevent this from happening? If i show the view controller straight away, this works no problem.

I'd recommend, that you add a call [notificationCenter removeObserver: self] in method dealloc of those classes, which you intend to use as observers, as it is the last chance to unregister an observer cleanly.

Related

Will observers automatically removed when observers become nil?

I'm uisng addObserver:selector:name:object: in viewDidLoad.
And I'm using removeObserver:name:object: in viewWillDisappear:animated: to remove observer.
What will happen if I failed to remove observer by passing wrong parameter to removeObserver:name:object:?
(For example, observer isn't removed if I pass wrong notification to parameter name or wrong object to object or Observer)
If the observer still not nil after calling removeObserver:name:object:, I can find out that removing observer failed because notificationSelector will being called.
But if the observer become nil after calling removeObserver:name:object:, I can not find out whether removing observer failed or not.
Will observers automatically removed when observer become nil?
Or does notification dispatch table of NSNotificationCenter became larger and larger and eventually the app become slow?
EDIT
When I use subclass of UIViewController object for observer, the app doesn't crash after ViewController's dealloc are called.
But when I use a object of other class, the app crashs after the object's dealloc are called.
Update: From -[NotificationCenter removeObserver:]:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call this method or removeObserver:name:object: before observer or any object specified in addObserverForName:object:queue:usingBlock: or addObserver:selector:name:object: is deallocated.
Old answer:
Observers are not removed automatically. From the NSNotificationCenter Class Reference:
Important: The notification center does not retain its observers,
therefore, you must ensure that you unregister observers (using
removeObserver: or removeObserver:name:object:) before they are
deallocated. (If you don't, you will generate a runtime error if the
center sends a message to a freed object.)
You should therefore call
[[NSNotificationCenter defaultCenter] removeObserver:self];
in your dealloc method if you are not 100% sure that the observer was not removed previously.
You just need to put in the correct Observer for the Observer to be removed. If you pass the wrong parameter to name or object (or nil), the receiver will not use them as criteria for removal.
All Cocoa programs have a default NSNotificationCenter, so once you remove the observers you shouldn't have to worry about the it taking up more memory.

Do observers need to be removed in iPhone applications

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.

UIViewController is popped from view stack and NSURLConnection crashes the application

I am pushing a UIViewController onto a UINavigationController. This view controller immediately starts a download of an xml feed and then parses it. However, if you hit the back button before it is done downloading, and crashes with EXC_BAD_ACCESS. The line that is crashing it is in parserDidEndDocument and is this line:
if (self.delegate && [self.delegate conformsToProtocol:#protocol(ModelDelegate)]) [self.delegate modelDidFinishParsing:self];
I assume it is crashing because it is trying to access self.delegate which is not assigned anymore. How do I get around this?
Also, I would release the model object in the modelDidFinishParsing method. How would I release this model if it never reaches this method.
I set up objects to handle my downloads (and other asynchronous or long running tasks) in the AppDelegate, then trigger them as required from various controllers. That way they are owned and have persistence through the life of the application.
The best way to do this is to pass them to the viewControllers that will need them (rather than the viewController "expecting" the appDelegate to have such and such an object ready and waiting) - dependency injection.
These objects update my model in some way when they finish and if I need to, I use NSNotifications to announce they are done. This isolates me from the mess I used to get into trying to cancel or swap delegates in viewWillDisappear etc to avoid the kind of issues you are running into.
The reason your app is crashing is probably because NSURLConnection retains its delegate (so it can call back to it reliably) but objects that this delegate has weak references to have been deallocated.
Ie, in your case what self.delegate points to has probably been deallocated when the view controller is popped but the delegate property has not been cleared (set to nil).
The solution to your problem is to clear (nil) self.delegate at the appropriate time when the UIViewController subclass is being popped off the navigation stack.
Note: retaining delegates is not usual behaviour for Cocoa classes. In situations where it happens contrary to standard practice it is documented (see the NSURLConnection docs).

What's a good place to unregister an observer from the notification center?

When I add an observer to the default notification center, where would I unregister that?
Example: I have a UIView subclass which lives inside a view controller. That subclass is an observer for the FooBarNotification. If this notification is posted, that view will get it. But now, the view controller decides to throw away the view. Is the best place the -dealloc method of the view itself?
Are there any rules like memory management rules? For example: Must I unregister an observer where I registered it? i.e. the view registers itself in it's init method, so it should unregister itself in it's -dealloc method?
(not talking about push notifications, but NSNotificationCenter)
The only rule is to make sure the observer lives longer than the registration period.
Since -addObserver:… will not -retain the observer, if the registration outlives the observer itself, your program will crash.
Apple doesn't specify any rules to unregister the observer. -dealloc is fine. Just use common sense. E.g. if that observer may persist even after the view controller throw it away, then you should unregister at that throw-away procedure, otherwise the observer may receive unwanted notifications.
Certainly it should be done in the dealloc, but basically when you no longer need notifications.
One possible place is to override the willMoveToWindow: message on the UIView and remove the notification when the view is removed from a window:
- (void)willMoveToWindow:(UIWindow *)newWindow
{
if (window == nil) {
// view removed from a window -- remove notifications here
}
[super willMoveToWindow:newWindow];
}
I usually add my observers in viewWillAppear and remove them again in viewWillDisappear.
This works because almost always I won't want a notification to be picked up by a view that isn't currently in the foreground (any longer living observers I register in my coordinator)
Dealloc and viewDidUnload if you have registered for notifications in viewDidLoad.

Invoking a method on a delegate after delay, from an object at the end of it's lifetime

I have a modal view controller that creates core data changes in it's own context, and when I click done, it saves the changes (that dispatches the merge changes notification), notifies the delegate and dismisses.
My problem is that I need the delegate to receive the message after my main context has merged with the changes of the editing context. I want the delegate call to take place on the next run loop but I'm having problems with object lifetimes. I've thought of the following:
Make call to [delegate performSelector:withObject:afterDelay:] however it seems that that message is not recognised. My delegate conforms to the NSObject protocol but that doesn't include the perform selector with delay.
Create a method in my view controller: informDelegateWithObject: that calls the delegate method, and call that method after a delay. I.e. [self performSelector:#selector(informDelegateWithObject:) withObject:.. afterDelay:..]. This could work, however, as my view controller is being dismissed, if the delay is several seconds then it would have been released from memory and wouldn't that cause a crash when it comes to invoking?
Create an instance of NSInvocation. I have thought about this, however, what is the lifetime of this object? If I create it using [NSInvocation invocationWithMethodSignature:] then wouldn't the NSInvocation object be autoreleased, and not be around for the next run loop? Let alone several seconds. And as my modal view controller is being dismissed and released, I can't store the invocation object in my view controller.
Any suggestions?
You should merge contexts into the delegate.
Say that you press Save into you modal controller: you will send a myViewController:didFinishSaving: to the delegate.
This delegate into myViewController:didFinishSaving: implementation will save, merge and dismiss the modal view controller.
I hope I have understood your problem.
Bye! :)
You might look at Apple's Core Data Books tutorial which works along the lines that muccy describes. Saving happens after the modal view is dismissed and control is returned to the parent view controller. The parent contains the update code and fires notifications required to merge changes (whether that happens in the delegate or elsewhere).
To question #1: performSelector:withObject:afterDelay: is defined in the NSObject class, not the NSObject protocol. Any object you are using is probably an instance of NSObject. You are probably referring to a compiler warning resulting from static type checking. (Technically, it's possible for an object that conforms to the NSObject protocol to not be an NSObject; NSProxy is one example. But any object you normally use will be an NSObject.) You can ignore this warning (in Objective-C, you can try to send any message to any object). Or, if you want, you can cast it to either id (which allows you to send any message without any warnings) or NSObject *.
To question #2: "if the delay is several seconds then it would have been released from memory" No, the documentation for performSelector:withObject:afterDelay: says "This method retains the receiver and the anArgument parameter until after the selector is performed."
You can also declare your delegate like this:
NSObject <MyClassDelegateProtocol> *delegate;
Then your delegate will also be an NSObject that conforms to your protocol.