NotificationCenter is observing multiple times - swift

I'm having a problem in my implementation using observers in Swift, The problem is I'm having the same observer multiple times so I make one function more than once.
How can I validate if I'm observing the same observer already before observing it again?
NotificationCenter.default.addObserver(self, selector: #selector(myFunction(notification:)), name: NSNotification.Name(rawValue: "SameObserver"), object: nil)
Thanks for your help

By making sure to balance your calls to addObserver and removeObserver. Alternately, you could create a boolean somewhere that indicates that you've already called addObserver, but it's better just to balance your calls.
The best way to balance your calls is to tie them to the observer's lifetime or lifecycle. For lifetime you would call addObserver in init and removeObserver in deinit (or use one of the forms that automatically removes your observation). For lifecycle (for example in a ViewController), you would call addObserver in viewWillAppear and removeObserver in viewDidDisappear.
There is no mechanism for querying NotificationCenter.

Related

How to synchronize multiple controls?

I am looking for pattern in swift to synchronize the state of multiple controls. Say I have NSSegmentedControl in the view, NSMenuItems with checkmarks and NSSegmentedControl in touch bar. Each of them is sending change to model. But how to synchronize with other two? I can use delegates but it looks like the complexity will grow with number of synchronizable items. Is there a common pattern to approach this problem? I would think of something like subscription to change, so all 3 controls can subscribe to it and receive notification when a change occurs, then use it's own transformation of data to pass to the control. Wonder if there is some mechanism for that.
There is a mechanism for that, it's the notification model. I imagine you want the view controller to listen for changes and update its controls--not have each control listen for changes and update itself (which is also possible). The basic approach would be to add a notification observer to the view controller (or whatever object you want to be the observer)...
NotificationCenter.default.addObserver(self, selector: #selector(nHandler(_:)), name: NSNotification.Name(rawValue: "someControlDidChange"), object: segmentedControl)
...and a handler to take action on that notification...
#objc func nHandler(_ notification: Notification) {
guard let control = notification.object as? UISegmentedControl else {
return
}
// determine which control posted the notification
// read the control's new value
// update the other controls
}
Then as a control updates its values, it posts notifications...
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "someControlDidChange"), object: self)
The object here can be self if this notification is posted from within the control (i.e. it's subclassed) or the instance of the control itself (i.e. someSegmentedControl).
The notification observer and the notification poster allow you to include an optional object, as you see in my examples. You should be passing some data within these notifications to allow the observer to determine what object is posting the notification and what value that object has been updated to. Then in the handler of the notification observer, you unwrap that object to get that data.
I have yet to benchmark the costs of posting notifications; I really have no idea how expensive it is. If the delegate approach is too messy and you've written off property observers, the notification model is likely the way to go.

Observer/Dealloc errors and crash with unseen UIViewController

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.

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.

performSelectorOnMainThread over Notifications

I have some basic doubt's, guess someone will help me out.
Please refer this Question : Update ULabel immediately while downloading files
I have tried using performSelectorOnMainThread , which is calling the updateProgress method in another class and but the label is not updating.
But now I have used the notification like
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateProgress" object:nil userInfo:nil];
which seems to be calling the method and also updating the UILabel. Eventhough my problem is resolved, I want to know why the above performSelectorOnMainThread didn't worked out for me? Any specific reasons ?
The class where you make the performSelectorOnMainThread call and the updateProgress method needed to be in the same class. If not, (in your case), create a method in the class where performSelectorOnMainThread was called and redirect it to the updateProgress on the other class.
The performSelectorOnMainThread can be used to run some codes in your main Thread. It not seems to be a method to call a method on another class (Even though you can call the method on another class using this if you have the working instance of the class). If you are using API calls in one of your class, you may have to use seperate threads for performing the API calls as it blocks the main thread (Its not kind to users who use your app). So in the ios you must call the UIKit from main thread only.
The NSNotification is used to get a event call. I mean it notifies the observer when occurs a specific event that the observer registerd to be get notified.
Hope this helps you.

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.