How to remove observer defined without object and using block? - ios

Within extension UIViewController I have a method:
func setupUserAndCartButtons() {
let cartBarButtonItem = UIBarButtonItem(image: DBCart.sharedCart().icon, style: .Plain, target: self, action: Selector("cartButtonTapped:"))
NSNotificationCenter.defaultCenter().addObserverForName(DBOrdersChangedNotficationName, object: nil, queue: nil, usingBlock: { [weak self] notification in
print("---->\(self!.classForCoder)")
cartBarButtonItem.image = DBCart.sharedCart().icon
})
}
I use it to change the image for my UIBarButtonItem.
This is how I push notification:
NSNotificationCenter.defaultCenter().postNotificationName(DBOrdersChangedNotficationName, object: nil)
For every controller, within deinit, I need to remove observer:
NSNotificationCenter.defaultCenter().removeObserver(self, name: DBOrdersChangedNotficationName, object: nil)
But it doesn't work:
When I push notification and deinit is never called (what means that I never pop controller from the stack) it looks ok, no crash, but once I pop at least one view controller (deinit is called then) and push notification, there is a crash:
fatal error: unexpectedly found nil while unwrapping an Optional value

You are using addObserverForName:object:queue:usingBlock: to setup your observer. In order to later remove this observer you will need to use the return value of that method as the "observer" in any calls to the removeObserver:... variants.
Since you are setting up the observer in an extension method of UIViewController, and assuming you are calling this from concrete UIViewController subclasses, I'd suggest updating this method to return the observer
func setupUserAndCartButtons() -> NSObjectProtocol {
let cartBarButtonItem = UIBarButtonItem(image: DBCart.sharedCart().icon, style: .Plain, target: self, action: Selector("cartButtonTapped:"))
let observer = NSNotificationCenter.defaultCenter().addObserverForName(DBOrdersChangedNotficationName, object: nil, queue: nil, usingBlock: { [weak self] notification in
print("---->\(self!.classForCoder)")
cartBarButtonItem.image = DBCart.sharedCart().icon
})
return observer
}
You will have to setup a property in your view controllers where you make use of this method in order to keep a reference to the observer:
class DBSomeViewController: UIViewController {
var ordersChangedObserver: NSObjectProtocol?
...
}
Then assign the return value of setupUserAndCartButtons to this property:
ordersChangedObserver = setupUserAndCartButtons()
And finally remove the observer in the deinit:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(ordersChangedObserver)
}
Credit
I can't take full credit for this answer as Dániel Nagy pretty much got it in his comment on the question.

Related

Swift - Timer not able to access updated instance property

Im my react native project's native module I need to send some data periodically from Objective-C to Swift so I am using NSNotificationCenter. I receive the data successfully in my Swift class, inside the function attached to the observer, and I store it in a property.
If I access this property from any instance method call I can see that the value has updated.
However if I access the same property in the selector function attached to the Timer it appears as if the value has not been updated and I cannot figure out why? It seems as if the timer selector function does not have access to anything except the initial value of the property - I have also tried passing the property as part of userInfo to the Timer but the issue is the same.
[[NSNotificationCenter defaultCenter] postNotificationName:#"stateDidUpdate" object:nil userInfo:state];
class StateController {
var state: Dictionary<String, Any> = Dictionary()
var timer: Timer = Timer()
func subscribeToNotifications() {
NotificationCenter.default.addObserver(
self, selector: #selector(receivedStateUpdate),
name: NSNotification.Name.init(rawValue: "stateDidUpdate"), object: nil)
}
#objc func receivedStateUpdate(notification: NSNotification) {
if let state = notification.userInfo {
self.state = (state as? Dictionary<String, Any>)!
print("\(self.state)") // I can see that self.state has been updated here
}
}
func runTimer() {
self.timer = Timer(timeInterval: 0.1, target: self, selector: #selector(accessState(timer:)), userInfo: nil, repeats: true)
self.timer.fire()
RunLoop.current.add(self.timer, forMode: RunLoop.Mode.default)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 2))
}
#objc func accessState(timer: Timer) {
print("\(self.state)") // state is an empty Dictionary when accessed here
}
func printState() {
"\(self.state)" // value printed is the updated value received from the notification
}
}
I figured out that multiple instances of my Swift class being created was causing the issue. I assumed that React Native would create a singleton when calling a native module but it appears multiple instances are created as I could see how many times the init method was called. Switching to a Singleton pattern resolved the issue for me following this excellent video and this excellent gist on how to create a singleton in a react native project
class StateController {
static let shared = StateController()
private override init() {
}
}

How to removeObserver in Swift 5 using addObserver closure method

This is my first post.
I'm Japanese iOS engineer (just became this month).
I have a trouble with removeObserver method of NotificationCenter in Swift 5.
I added observer to ViewController (VC) by using closure type addObserver.
I want to remove this Observer when VC's deinitialization has called.
I wrote NotificationCenter.default.removeObserver(self) in VC's deinit method. But, this seemed not to work for me.
What's the problem???
Additionally, if my code has memory leak problem, let me know how to fix it.
Here's piece of my code.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] notification in
guard let self = self else { return }
self.loadWeather(notification.object)
}
}
deinit {
print(#function)
print("ViewController died")
NotificationCenter.default.removeObserver(self)
}
}
Closure-based addObserver
If you use the closure-based variant of addObserver, the token (not self) is
the observer. The notification center has no knowledge at all about
self in this situation. The documentation is not super clear on
this.
from Twitter
Meaning it's meaningless to do: NotificationCenter.default.removeObserver(self)
The docs recommend two ways:
Normal way
Subscribe as you normally do:
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: \(NSLocale.currentLocale().localeIdentifier)")
}
Remove the observer at some point of code.
NotificationCenter.default.removeObserver(self.localeChangeObserver) e.g. through a function or in deinit
Note: You'd still need to use [weak self] to avoid retain cycles.
To avoid a retain cycle, use a weak reference to self inside the block when self contains the observer as a strong reference.
Single subscribe
Remove the observer immediately after the first time it gets a callback
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
var token: NSObjectProtocol?
token = center.addObserverForName("OneTimeNotification", object: nil, queue: mainQueue) { (note) in
print("Received the notification!")
center.removeObserver(token!)
}
Selector-based addObserver
If you use the selector-based add, self (there is no token) the observer. Having that said, you should avoid doing:
NotificationCenter.default.removeObserver(self)
because your code may not be the only code adding observers that involve the object. When removing an observer, remove it with the most specific detail possible. For example, if you used a name and object to register the observer, use removeObserver(_:name:object:) with the name and object.
It’s only safe to call removeObserver(something) in the deinit method, other than that don’t use it use removeObserver(_:name:object:) instead.
Calling removeObserver(self) is incorrect outside deinit, because you’d be removing all observations set for the object. Calling it inside deinit isn’t incorrect, but meaningless, because the object is to be deallocated immediately.
Set your observer object to current view controller.
From apple doc.s, object is
The object whose notifications the observer wants to receive; that is,
only notifications sent by this sender are delivered to the observer.
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification,
object: self,
queue: nil) { [weak self] notification in
guard let self = self else { return }
self.loadWeather(notification.object)
}
Removing observer from NotificationCenter
deinit {
NotificationCenter.default.removeObserver(self)
}
ANOTHER WAY
You can also make copy of Notification Observer object and remove it from NotificationCenter in deinit.
let notificationCenter = NotificationCenter.default
var loadWeatherObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
loadWeatherObserver = notificationCenter.addObserver(forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: nil) { [weak self] notification in
guard let self = self else { return }
self.loadWeather(notification.object)
}
}
deinit {
if (loadWeatherObserver != nil) {
notificationCenter.removeObserver(loadWeatherObserver!)
}
}

How to register Notification Center in class iOS

I am new to ios programming, I wanted to register notification center in the class, not in the view controller, I want to send some action from one view controller this custom class receives that action and it performs some view controller navigation.I wanted to have custom notification center in ios.
code:
NotificationCenter.default.post(name: Notification.Name(rawValue: "key"), object: nil)
class MainReceiver: NotificationCenter
{
override func post(name aName: NSNotification.Name, object anObject: Any?)
{
print("coming here")
}
}
Here you have snipped code:
class MainReceiver {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleMethod(_:)), name: NSNotification.Name(rawValue: "Notification_Name"), object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func handleMethod(_ notification: Notification) {
// handle
}
}
From Apple's NSNotificationCenter documentation:
Each running Cocoa program has a default notification center. You typically don’t create your own.
I'm not sure why you think you want to create your own notification center, but trust me, you don't.
However, you can easily subscribe an object (an instance of a custom class, like you say you want to use) to notifications:
// let object = Whatever()
NotificationCenter.default.addObserver(object, selector: #selector(didReceive(notification:)), name: NSNotification.Name(rawValue: "key"), object: nil)
// object's didReceive(notification:) function will now be called when a notification with the specified name is posted. This can be posted from anywhere.
A while back, the function had to be decorated with the #objc attribute. I'm not sure if that's still the case, but if so, you'd use #objc func didReceive(notification: Notification) as your declaration in the receiving class.
If I understand your question correctly I think what you are looking for is the blocked based variant for the NotificationCenter.
You can find it in the official docs.
Example code from the docs:
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: \(NSLocale.currentLocale().localeIdentifier)")

iOS unable to remove Notification observer. Deinit not getting called

I have a UIView similar to the one you can see below:
class ViewTaskViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
subscribeToNotifications()
}
func subscribeToNotifications() {
let notification = NotificationCenter.default
notification.addObserver(forName: Notification.Name(rawValue: "TimerUpdated"), object: nil, queue: nil, using: handleUpdateTimer)
print("Subscribed to NotificationCenter in ViewTaskViewController")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("TUFU TUFU TUFU")
NotificationCenter.default.removeObserver(self)
}
deinit {
print("DENINT")
}
#objc func handleUpdateTimer(notification: Notification) {
if let userInfo = notification.userInfo, let timeInSeconds = userInfo["timeInSeconds"] as? Int {
withUnsafePointer(to: &self.view) {
print("We got timeeeeee \(timeInSeconds) \($0)")
}
//do something here....
}
}
}
The issue I am having is that I am unable to remove the observers from this particular UIView when the user hits the back button and returns to another viewController.
ViewWillDisppear is called but deinit is not called. The strange thing is that if we remove subscribeToNotifications() from viewDidLoad() then the deinit is called.
The other issue is related to a memory leak. As you can see in the screenshot below, when the view does subscribe to notifications and the user leaves/re-enters the view, the memory usage increase.
Now compare that to when the subscribeToNotifications() is commented out, there is no increase in memory usage and only one instance of the viewController.
The conclusion is that there seems to be a correlation between the notification subscription creation of a new instance of the UIView hence the deinit is not being called.
I'd like to find out if there is a way we can deinitialize the view and unsubscribe from the notification.
Please let me know if you need further information. :)
I've found the removeObserver() only works if you use this version of addObserver()
notification.addObserver(self, selector:#selector(self.handleUpdateTimer), name: Notification.Name(rawValue: "TimerUpdated"), object: nil)
I'm guessing with the original version you aren't actually indicating who the observer is.
As #Spads said you can use
NotificationCenter.default.addObserver(self, selector: #selector(subscribeToNotifications), name: NSNotification.Name(rawValue: "TimerUpdate"), object: nil)
or the one you already have.
you can remove your notification by it's name or it's reference
NotificationCenter.default.removeObserver(self, name: "TimerUpdate", object: nil)
if you declared your notification at the top of your class then you can directly pass the reference of your notification to be removed in your case notification
NotificationCenter.default.removeObserver(notification)
You should store your newly added observer in a opaque object (NSObjectProtocol) and then call NotificationCenter.default.removeObserver(self.nameOfObserver)

ARC not working properly when using NSNotificationCenter

Why doesn't deist get called on an object that has used NSNotificationCenter, I have included below a simple version of my code. Where I create an object that observes for a notification and when the notification is fired, it removes the observer's subscription. I also remove the subscription if the object is freed up. However, when running profiling for the app, you can see that after viewDidAppear finishes there is a persistent allocation for the test object that is now nil and should have been freed up. Why is this the case?
import UIKit
class ViewController: UIViewController {
var t: test?
override func viewWillAppear(animated: Bool) {
t = test()
fire()
t = nil
}
func fire() {
NSNotificationCenter.defaultCenter().postNotificationName("Hello",
object: nil)
}
}
class test {
var e: NSObjectProtocol?
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName(
"Hello", object: nil, queue: NSOperationQueue.mainQueue(),
usingBlock: sayHello)
}
deinit {
if let e = e { NSNotificationCenter.defaultCenter().removeObserver(e) }
}
func sayHello(notification: NSNotification) {
if let e = e { NSNotificationCenter.defaultCenter().removeObserver(e) }
}
}
I would appreciate an answer even in Objective-C, since it will probably answer this question as well.
Thank you very much
Passing in a function of self as a closure parameter will create a retain cycle.
What you're doing is effectivity short hand for:
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName("Hello", object: nil, queue: NSOperationQueue.mainQueue() { notification in
self.sayHello(notification)
}
}
As you can see self is being captured here. To get around this you should defined self as unowned in a capture list:
init() {
e = NSNotificationCenter.defaultCenter().addObserverForName("Hello", object: nil, queue: NSOperationQueue.mainQueue() { [unowned self] notification in
self.sayHello(notification)
}
}
This will prevent the retain cycle.
As you're removing the observer in sayHello you should also set e to nil in there too after removing the observer.
See this question for more info about retain cycles, capturing, etc. when using this method on NSNotificationCenter.
you don't add self as observer but another block.
BUT
Then you remove yourself (though never added) but forget the block
--- so:
self.observer = center.addObserverForName(didEnterBackground, object: nil, queue: nil) {
...
}
then later
center.removeObserver(self.observer)

Resources