Remove all observers from a class - ios

In a project I have a class say "A", there are many other class that are observing class "A"'s property values.
Sometimes the class "A" instance gets deallocated and observees fails resulting in crash!
Is there any way to remove all the observers from class "A"? Something like this:
-(void) dealloc{
[remove allObservers forKey:#"theKey"];
}

In short, sadly no. KVO is made so that it gets you in the end, unfortunately.
I've struggled with this thing before, and I found the following two solutions:
Use a proxy method to register for observation in your observee, which will maintain a list of weak references to observers.
Ideally, you'd want a proxy method for removing observers as well, so that your list is updated accordingly (although, since they are weak references in your list, it wouldn't hurt if some observer removed itself using standard KVO instead of your proxy method, and then deallocd itself).
In case your observee gets deallocated, it should inform all observers (using a protocol), or just remove them outright itself. For the last one, using exceptions might come in handy as well (I am aware that exceptions are evil in Obj-C, but what to do):
#try
{
[self removeObserver:observee forKeyPath:#"path"];
}
#catch (NSException * __unused exception) {}
Use some abstraction from KVO. There are a couple of projects that come to mind such as RZDataBinding and MAKVONotificationCenter (despite it's name, it actually pertains to KVO)

You should keep "A" class alive until there are other objects observing its property values. Maybe it gets deallocated because you're not correctly handling its reference.
You should check if "A" needs a 'strong' reference. When you don't need "A" anymore (i.e.: you're popping a view controller, you are refreshing a table, you're clearing a scrollview), you should also remove any observer attached to it (and be able to do it).

Related

What is the appropriate place to remove KVO observer for a Managed Object

I'd like to monitor property value change of a managed object in Core Data for data integrity purpose (for example, if a specific property changes, another object in a relationship should be changed accordingly). I'm thinking to implement it using KVO and let the managed object observe its own property change. Below is my code.
extension Book {
override public func awakeFromInsert() {
super.awakeFromInsert()
observation = self.observe(\.date, options: [.old, .new]) { book, dateChange in
print("date is changed!")
}
}
override public func awakeFromFetch() {
super.awakeFromFetch()
observation = self.observe(\.date, options: [.old, .new]) { book, dateChange in
print("date is changed!")
}
}
}
(Note: the observation variable is a transient attribute of the managed object to keep a strong reference to the NSKeyValueObservation object.)
My question is what's the appropriate place to remove observers? Or is it necessary at all? I did try the above code and it worked fine. But I'm not sure if it just happened to work and may have potential issue when code becomes complex.
I have figured out the following after some research:
1) App should remove KVO observers before a NSObject is deallocated. For example, the doc says:
An object that calls this method must also eventually call either the removeObserver(:forKeyPath:) or removeObserver(:forKeyPath:context:) method to unregister the observer when participating in KVO.
Note: the doc is about addObserver(), not the API I'm using, but I think the idea is same.
2) The new KVO API in Swift removes the observer automatically when NSKeyValueObservation object is deinitialized (i.e., when it's out of scope or not referenced by others).
So I thought I should do this:
extension Book {
deinit {
observation = nil
}
}
But unfortunately that is impossible because XCode gave this error:
Deinitializers may only be declared within a class
So I wonder what's the usual way to do it? Could it be that, since the the observer and observee are same object, the underlying KVO framework code notices that observer object is also being deallocated, so it's OK without removing the observer explicitly?
Any suggestion will be much appreciated.
Update 1:
#pbasdf suggests using willTurnIntoFault. I google about it and find few discussions which indicate people using it this way. My main confusion: is fault a required a state before a NSManagemObject is deallocated? I don't find Apple doc says so (the doc doesn't have a state transition diagram).
EDIT: According to this post, when a NSManagedObjectContext is tore down, it turns all objects registered with it back into faults. If so, fault does always occur before deallocation.
BTW, while reading Apple doc, I also find this:
When Core Data turns an object into a fault, key-value observing (KVO) change notifications are sent to the object’s properties. If you are observing properties of an object that is turned into a fault and the fault is subsequently realized, you receive change notifications for properties whose values have not in fact changed.
That leads me to think using KVO with NSManagemObject is perhaps not a ideal approach because it is interfered by fault.
Update 2:
Since the observer and observee is the same object in my case, there is a much simpler approach than using KVO. That's defining a wrapper of property setter method and implementing all the logic in the wrapper. I'll use this approach.
That said, I still think my question is valid in general case where observer and observee are different objects.
Update 3:
#matt pointed out that it's OK to not remove KVO observers since iOS11. See another discussion here and official doc here.
Relaxed Key-Value Observing Unregistration Requirements
Prior to 10.13, KVO would throw an exception if any observers were still registered after an autonotifying object's -dealloc finished running. Additionally, if all observers were removed, but some were removed from another thread during dealloc, the exception would incorrectly still be thrown. This requirement has been relaxed in 10.13, subject to two conditions:
The object must be using KVO autonotifying, rather than manually calling -will and -didChangeValueForKey: (i.e. it should not return NO from +automaticallyNotifiesObserversForKey:)
The object must not override the (private) accessors for internal KVO state
If all of these are true, any remaining observers after -dealloc returns will be cleaned up by KVO; this is also somewhat more efficient than repeatedly calling -removeObserver methods.

Im confused on how can I manipulate the properties inside an instance when it gets deinitialized?

I have this from either the Apple documentation or the swift book
When an instance gets deinitialized, you still have access to the
properties inside the instance and can manipulate them as needed
before the instance totally goes away.
I'm confused, do they mean when we for example do some mathematical action using the instances property in the deinit() method? or lets say when we print a property of type string that was part of a specific instance, also from the deinit() method?
If so, then is the deinit() method the only way to manipulate a property when it is being deinitialized?
if you have a swift class with a a var you know you have to clean up after because ARC can't free it (e.g. C memory), you can still do that in deinit. The pointers stored in the properties are still valid!
it isn't useful for much more though (ok end observing with the notification center or kvo) BECAUSE there is no guarantee WHEN deist is called. ONLY that it is called before deallocation [whenever that is]
deinit is called right before deallocation (when the retainCount reaches 0), so all your properties are still valid and you can print your string. You don't need to set properties to nil explicitly in deinit as that happens automatically.
This being said, most classes don't even need deinit implemented
Most of the time I used deinit to remove observer that the instance is registered to, post any notifications if needed, and things like that.
As far as I know, the deinit method gets called just before the instance gets deinitialized, to give you a final opportuninty to do whatever you need to do (cleanup, close a file, terminate a network connection, etc).
What the documentation says is that, at the time deinit is called your object has not been deinitialized yet (but will be very soon), so you still can (for the last time) access its properties.

How do I make sure to removeObserver without subclassing and overrideing dealloc?

When using the NSNotificationCenter object, if you add an observer for a notification, You must invoke removeObserver: or removeObserver:name:object: before any object specified by addObserverForName:object:queue:usingBlock: is deallocated.
Is there a way to do this without subclassing the object and overriding "dealloc"? I don't want to have to subclass a bunch of objects just to use notifications with them if I can avoid it. Is there some other way to learn about the dealloc before it happens?
Thanks for your help.
Generally speaking you should think about what object is observing changes and make it appropriate. This usually means a controller class, and in this case you usually already have a custom subclass. It should usually be receiving the observation callbacks and forwarding them as required. It should add and remove itself as an observer when it is created / destroyed / put on display / removed from display.
That said, if you persist with making arbitrary classes observers, here's an idea (I think it should be safe, though I wouldn't want to rely on it):
You can use a single custom class to handle the removal of the observer. This should be a class which is configured with a reference to the class to be removed. During the configuration method this class adds itself to the class to be removed using objc_setassociatedobject and OBJC_ASSOCIATION_RETAIN, and stores the pointer to the class to be removed in an NSValue. Then, when it is deallocated, it removes the other class from the notification center, using the NSValue.

what happens if delegate doesnot exist anymore?

I came around a interesting issue while working on my app. Imagine the scenerio where
There exist one object, Lets call it A.
A, then creates an object(B) of some delegation based class, say NSURLConnection.
A assigned itself as delegate of B, provided A has implemented all the required delegate methods.
A asks B to start its processing. In our example i.e. fetching data from some server.
As soon as B finished fetching data, it will call some specified method of A.
In the last step, suppose while calling the methods of A, B finds that the A object doesnt exist anymore. Then what happens???
I'm not sure but does it cause crash?
If yes, then please suggest me how to avoid the situation.
In my case I assigned the viewcontroller as delegate of some object, say X in viewDidLoad method. There are cases when viewcontroller get destroyed before X calls the delegate methods defined in the viewcontroller.
If assigning X's delegate to nil solves the problem. Then where
should i do that.
In short, which method is called only once while unloading phase of view controller likewise
viewDidLoad in its loading phase.
The best way to achieve this kind of communication between classes (where class A could be deallocated at any time) is listening to NSNotifications.
As you stated , using weak(assign) delegates is dangerous and requires extra thought.
Using strong delegates could as well create a memory bloat (why should we retain a view controller so long after popping it from the view anyway?).
For more on NSNotificationCenter and notifications , you can find a lot of info in the SDK docs.. for specific questions, you know where to ask..
You should not reach a situation where one object holds a reference to another object which may be deallocated somewhere else without the owner object being notified.
Either when deallocating object A notify object B (by making member a nill in object B for example) or modify your design/flow to never allow A to be deallocated before B finishes (e.g. retain A when assigning as a delegate in B if possible)
Checking against a valid delegate object should be sufficient enough.
if (delegate)
[delegate method];

Multiple Delegates in iOS

I am making an object that goes to download stuff for all of my view controllers. The object is singleton instance and has a callback method with received data once the download is completed. It also has a delegate property so that it knows which object to call back to after the download is done.
There are multiple controllers that use this shared instance, and my question is how to call back to the correct view controller that requested the download.
My approach is to use delegation, but the problem is that since other view controllers are also its delegate, the download object could call back to every object and this will be hard to track.
I've worked on projects where people have attempted to use multiple delegates and it's basically a bad idea. The delegate pattern is about a 1 to 1 relationship between a class and it's delegate. Whilst it is possible to achieve some level of multiple delegation through switching the delegates in and out, it's more likely to lead to unpredictable behaviour and bugs.
My recommendation would be to change how you are thinking about this. You have two options as I see it:
Switch to an Observer pattern where you can register multiple observers which your main class can interact with. This is useful where your observers all implement the same protocol and where your main class wants to be aware of the observers and interaction with them.
Broadcast NSNotifications to indicate state changes and events. Here is a more decoupled approach because the main class does not need to know who is listening and does not directly interact with them. Other can start and stop being notified at their leisure. It also has the advantage that you do not need to create or implement a separate protocol. Instead you register the classes that need to know about changes with the NSNotificationCenter which in turns handles all the routing of notifications for you.
It actually sounds like the delegate pattern might not be the best approach here.
I would look into NSNotificationCenter instead.
The basic idea is that your singleton doing the net connection posts a notification (with something like postNotificationName:object:userInfo:) , saying that new data is available. Within this notification, you can pass a dictionary object (userInfo) that holds the data you've fetched, or info on what parts of your Model contain updated data.
Then, your other view controllers can register themselves to 'observe' these notifications by calling addObserver:selector:name:object:. Generally speaking, when a vc becomes visible I call addObserver, and removeObserver when it's being hidden or transitioned out.
Good luck!
Delegation doesn't seem like the right solution to this problem. How about requiring the requesting view controller to provide an object (its self) and a selector for you to call as a completion notification? Of course, you'll need a place to store that object and selector until the download completes. Hopefully you have (or could create) an object for this.
i recommend to use one of these ways
observer:
when use data that you want to inform other object are near to primitive ones.for example when you are using 'NSMutableArray' you can not inform the change in one of object by the standard implemented pattern at least you need to implement one for your self that is not reusable that much
Notification
when your interaction with destination object (those need to be inform) is in one-way.it means you don't need any acknowledge or other data back from them.
delegate
when there is one object to inform at each time step.
note:block use for success and fail is not a pattern to broadcast data its about to queue task when you don't know when they are finishing or failing like network operations
EDIT:
how to create notification | multi delegate issues and implementation
While I agree with most of the answers here, if you did actually want to achieve multiple delegates you could potentially declare an array of delegates and send messages to all delegates within that array. If your protocol has optional delegate methods you safely check using responds(to aSelector: Selector!) -> Bool before invoking (being mindful of memory management, as those delegates would be strongly referenced in the array). Again I do agree that multiple delegates is likely a bad architectural idea and using blocks or notification center would suit your needs better.
One approach, which works for me if you only have one other object to forward messages to is to create a forwardingDelegate This does not end up with issues of hard to debug ordering of delegates and it does not unnecessarily create a dependency on the other object. Keep in mind, if you have many objects then this might not be the best approach, it is mainly for one additional object but this could be extended to support an array of objects so long as there is one that receives the SDK and forwards it to the other objects [1]. Note that every method that is needed for the forwarded object needs to pass it along, even if it is not used by the forwarding object.
For example, if I need to forward the messages coming from the mapView delegate:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
// handle this object here.
if ([self.forwardingDelegate respondsToSelector:#selector(mapView:regionDidChangeAnimated:)])
{
[self.forwardingDelegate mapView:mapView regionDidChangeAnimated:animated];
}
// or handle this object here.
}
[self.forwardingDelegate mapView:mapView regionDidChangeAnimated:animated];
The forwarding property would be declared like this.
#property (nonatomic) id<MKMapViewDelegate> forwardingDelegate;
And the other object would adopt the protocol as if it were receiving the original message.
[1] The array approach for multiple delegates may get tricky because then you don't have as much control over what order the delegates get called, as was mentioned in other posts.

Resources