How to remove a notification observer set in a closure in Swift? - ios

I have this code:
alert.addTextFieldWithConfigurationHandler { field in
field.placeholder = "password"
field.secureTextEntry = true
NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification,
object: field, queue: NSOperationQueue.mainQueue()) { n in
delete.enabled = field.text?.characters.count >= 2 }
}
I'm wondering how I should go about removing the observer from "field" when the containing view is dismissed? Since 'field' only exists inside the closure, I'm not sure how to get a reference to it that I can pass to removeObserver later.
Or do I even need to remove the observer? Will it just remove itself when field is eventually deallocated?
Update
I realized that in this specific case, I can retrieve the reference to the field and remove the observer by doing:
if let alert = alert, textFields = alert.textFields where textFields.count > 0 {
NSNotificationCenter.defaultCenter().removeObserver(textFields[0])
}
But I'm going to leave the question up for a little because I'm curious about the general case where I do
{
let x = Object()
NSNotificationCenter.defaultCenter().addObserverForName("thing",
object: x, queue: NSOperationQueue.mainQueue())
}()
Update to the update
Per #rmaddy, my update was wrong. I was confusing the observer and the observing object.
Per #leo-dabus I should be doing
weakSelf?.observer = NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification,
object: field, queue: NSOperationQueue.mainQueue()) { n in
delete.enabled = field.text?.characters.count >= 2 }
And then later in the action that dismisses the alert:
if let observer = weakSelf?.deleteAlertObserver {
NSNotificationCenter.defaultCenter().removeObserver(observer)
}

It looks like the consensus answer here is that we should create a variable in the outer scope to hold the reference to the observer that's created in the inner scope. Then when the observer is no longer needed, we can remove it. So, something along the lines of:
import UIKit
class Pointless
{
var observer: NSObjectProtocol?
init () {
class SomeObject {}
observer = NSNotificationCenter.defaultCenter().addObserverForName("something",
object: SomeObject(), queue: NSOperationQueue.mainQueue()) { n in
print(n)
}
}
deinit
{
if let observer = observer{
NSNotificationCenter.defaultCenter().removeObserver(observer)
}
}
}

Related

How to listen for change in variable inside a function

I have a class called Observers to observe Firebase Storage Upload Tasks, but before observing the progress, it waits for PHPickerviewcontroller to upload the video. I have an instance variable in my class, hasUploaded so that I can know when I can start to change the progress bar, however, with the way it's set up, the block of code in the if statement will never be called. I know there is didSet but that doesn't help me in this case, because I need to listen for change inside the function. How do I do that?
func observeProgress(progressWheel: UIActivityIndicatorView, errorLabel: UILabel, progressView: UIProgressView, progressLabel: UILabel)
{
progressLabel.text = "Downloading from Device...Please Wait (1/2)"
progressWheel.startAnimating()
progressWheel.alpha = 1
inProgress = true
//RIGHT HERE - Wait for hasUploaded to == true
if hasUploaded
{
progressWheel.alpha = 0
self.taskReference!.observe(.progress)
{ (snapshot) in
guard let progress = snapshot.progress?.fractionCompleted else { /**alert**/ return }
progressView.progress = Float(progress)
progressLabel.text = "\(round(100 * Float(progress)))% (2/2)"
if progress == 1
{
progressLabel.text = "Upload Successful!"
progressLabel.textColor = .black
progressView.progress = 0
}
}
}
}
I thought about it again and maybe it is easier for you to use the NotificationCenter.
In the Model or ViewController add
let nc = NotificationCenter.default
and thenadjust the hasUploaded variable to
var hasUploaded = false {
didSet {
let statusChange = ["userInfo": ["hasUploaded": hasUploaded]]
NotificationCenter.default
.post(name:
NSNotification.Name("com.user.hasUploaded"),
object: nil,
userInfo: statusChange)
}
}
In the controller with the function observeProgress, also add
let nc = NotificationCenter.default
Add the following to the viewDidLoad() function
NotificationCenter.default
.addObserver(self, selector:#selector(hasUploadedNotificationReceived(_:)),
name: NSNotification.Name ("com.user.hasUploaded"),
object: nil)
}
Finally create the function hasUploadedNotificationReceived() (which is called above whenever the notification will be received) to add the magic that should happen after the the change. For example:
#objc func hasUploadedNotificationReceived(_ notification: Notification){
let notification = notification.userInfo?["userInfo"] as? [String: Bool] ?? [:]
if (notification["hasUploaded"] as? Bool)! {
observeProgress(...) {
[...]
}
}
}
Please read also the documentation to figure out what options you have and what you can add or modify.
Beside this implementation, I also can imagine that the a Delegate as well as Combine and as #matt mentioned async/await could help to achieve your desired behavior.

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!)
}
}

swift: Listen for "struct variable"-change event?

I got a small problem :
How is it possible to listen for the change of a struct-instance variable declared in another ("uneditable!!") class?
I added some small code snippets to maybe clarify my thoughts.
instructions:
FixedClass is a uneditable class: I don't want to change / I'm not able to change any code of the "FixedClass"
EditableClass can be edited - I'm sure you will get it looking at the code (^_^)/
code:
let fixedClass: FixedClass = FixedClass()
class FixedClass {
struct MyObject {
var abc = 0
}
var instance = MyObject()
public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil, repeats: true) }
#objc func updateVar() {
instance.abc+=1
}
func getVar() -> Int {
return instance.abc
}
}
let editableClass: EditableClass = EditableClass()
class EditableClass {
public init() { }
func listenForChange() {
// listen for change event of FixedClass.getVar()
print("Variable: \(fixedClass.getVar())")
}
}
Calling with: editableClass.listenForChange()
To sum that up I'd like to listen for the change of the FixedClass.getVar() result - preferably avoid using loops or timers. However the most important thing is to get it working at least.
Any help would be very appreciated, thanks!
This is going to depend totally on how the 'real' FixedClass is defined. I.E. is it a subclass of NSObject, is it an ObjectiveC class, how the property you want to observe is defined.
As far as your actual example is concerned you could do it by subclassing like this:
var newFixedClass: NewFixedClass = NewFixedClass()
var editableClass: EditableClass = EditableClass()
protocol NewFixedClassProtocol: class {
func changed(newFixedClass: NewFixedClass)
}
class NewFixedClass: FixedClass {
public weak var delegate: NewFixedClassProtocol?
override var instance: FixedClass.MyObject {
didSet {
self.delegate?.changed(newFixedClass: self)
}
}
}
class EditableClass: NewFixedClassProtocol {
public init() {
newFixedClass.delegate = self
}
func changed(newFixedClass: NewFixedClass) {
print ("Value = \(newFixedClass.instance.abc)")
}
}
So you basically create a protocol which the class doing the observing supports, create a subclass of the FixedClass which has a delegate of the protocol type and overrides the property of the FixedClass you want to observe with a didSet observer which then calls the delegate method. At some point you have to assign the class observing as the delegate of the sub class (I did it in the init method as a test).
So doing that I can observe when the structure changes but I haven't touched the FixedClass.
Note however that this method relies heavily on knowing about the original FixedClass so may not work for your 'real world' case.
(Also as an aside I couldn't get it to work with the globally defined instances of the classes and had to set them inside my initial view controller but that could be to do with how I was testing and doesn't alter the method involved)
A couple of things:
If the original class was Objective-C or otherwise participated in KVO (e.g. Swift dynamic properties of NSObject subclass, etc.) then you could observe changes. But but that's a fairly narrow use case. But that's a general pattern for making one's properties observable by other objects. For more information, see the Key-Value Observing Programming Guide.
If you can’t edit the class, in some narrow cases you could theoretically subclass it and add whatever observation system you want. That obviously only works if you’re manually instantiating FixedClass and it is contingent upon how FixedClass was implemented, but in some narrow cases, you can achieve what you need via subclassing.
You asked:
Would you be so kind an share some code snippets with us?
Sure, consider your FixedClass:
class FixedClass {
struct MyObject {
var abc = 0
}
var instance = MyObject()
public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil, repeats: true) }
#objc func updateVar() {
instance.abc+=1
}
func getVar() -> Int {
return instance.abc
}
}
You could then define a subclass:
class FixedClassSubclass: FixedClass {
static let changeNotification = NSNotification.Name(rawValue: Bundle.main.bundleIdentifier! + ".FixedClassSubclassNotification")
override func updateVar() {
super.updateVar()
NotificationCenter.default.post(name: FixedClassSubclass.changeNotification, object: self)
}
}
Then you could do:
let fixed = FixedClassSubclass()
and
NotificationCenter.default.addObserver(forName: FixedClassSubclass.changeNotification, object: nil, queue: nil) { _ in
print("changed")
}
You can use whatever notification process you want. NotificationCenter. KVN. Delegate-protocol pattern. Whatever. The details of this will vary entirely based upon the details of FixedClass and you have given us a contrived example that is unlikely to be extensible in many situations.
I must confess to some general misgivings to the idea of trying to hook into the internal implementation details of a non-editable class. We generally strive for loosely coupled objects that only rely on published, supported interfaces. This endeavor violates both of those objectives. But I'll assume you have some good reason to do what you're attempting to do.
One way to do this would be to use NotificationCenter to broadcast the change, and have your EditableClass listen for that change and react to it. Your implementation could look something like this:
class FixedClass { //Class names should start with capital letters
struct MyObject { //Struct names should also start with capital letters
var abc = 0
}
var instance = myObject()
public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil, repeats: true) }
#objc func updateVar() {
instance.abc+=1
//Broadcast the change, and pass this FixedClass instance
NotificationCenter.default.post(name: Notification.Name("fixedClassChanged"), object: self)
}
func getVar() -> Int {
return instance.abc
}
}
Then you could react to this broadcast in your EditableClass like so:
class EditableClass {
public init() { }
func listenForChange() {
//Observe same notification being broadcast by the other class
NotificationCenter.default.addObserver(self, selector: #selector(processChange(_:)), name: Notification.Name("fixedClassChanged"), object: nil)
}
#objc func processChange(_ sender: Notification) {
//When the notification comes in, check to see if the object passed in was a FixedClass, and if so process whatever needs to be processed
if let fixed = sender.object as? FixedClass {
print("Class changed to \(fixed.getVar())")
}
}
}

Add property observer to global variable inside class in Swift

I have a variable globalVariable declared at global scope that may change at any time.
Different ViewControllers in my app need to react differently, when globalVariable changes.
Thus it would be desirable to add a property observer in each ViewController that execute the needed code when globalVariable changes.
I cannot seem to achieve it with override or extension. What is the way to go here?
If your goal is to simply know when your global variable changed, you could have it post a notification upon change:
extension NSNotification.Name {
static let globalVariableChanged = NSNotification.Name(Bundle.main.bundleIdentifier! + ".globalVariable")
}
var globalVariable: Int = 0 {
didSet {
NotificationCenter.default.post(name: .globalVariableChanged, object: nil)
}
}
Then any object can add an observer for that notification:
class ViewController: UIViewController {
private var observer: NSObjectProtocol!
override func viewDidLoad() {
super.viewDidLoad()
// add observer; make sure any `self` references are `weak` or `unowned`; obviously, if you don't reference `self`, that's not necessary
observer = NotificationCenter.default.addObserver(forName: .globalVariableChanged, object: nil, queue: .main) { [weak self] notification in
// do something with globalVariable here
}
}
deinit {
// remember to remove it when this object is deallocated
NotificationCenter.default.removeObserver(observer)
}
}
Note, this didSet mechanism will not detect changes if (a) the global variable is a reference type, i.e. a class; and (b) it merely mutates the object that the global variable references rather than replacing it with a new instance. To identify that scenario, you need to use KVO or other mechanism to detect mutation.
There can be only one didSet{} function for your global variable and it must belong to the variable itself. What you can do is make the variable's didSet{} function call a list of functions from other objects.
You could use notifications for this or you could build your own mechanism.
Here's an example of how you could create your own mechanism:
(note that this is pretty generic and could work for any variable types or singleton instance)
// Container for an observer's function reference
// - will be used to call the observer's code when the variable is set
// - Separates the object reference from the function reference
// to avoid strong retention cycles.
struct GlobalDidSet<T>
{
weak var observer:AnyObject?
var didSetFunction:(AnyObject)->(T)->()
init(_ observer:AnyObject, didSet function:#escaping (AnyObject)->(T)->())
{
self.observer = observer
didSetFunction = function
}
}
// Container for a list of observers to be notified
// - maintains the list of observers
// - automatically clears entries that non longer have a valid object
// - calls all observers when variable changes
// - erases type of observer to allow generic use of GlobalDidSet<>
struct GlobalDidSets<T>
{
var observers : [GlobalDidSet<T>] = []
mutating func register<O:AnyObject>(_ observer:O, didSet function:#escaping (O)->(T)->())
{
let observer = GlobalDidSet<T>(observer)
{ (object:AnyObject) in function(object as! O) }
observers.append(observer)
}
mutating func notifyDidSet(_ oldValue:T)
{
observers = observers.filter{$0.observer != nil}
observers.forEach{ $0.didSetFunction($0.observer!)(oldValue) }
}
}
...
// To use this, you will need a second variable to manage the list of observers
// and your global variable's didSet{} must use that observer list
// to perform the multiple function calls
//
var globalVariableDidSets = GlobalDidSets<String>()
var globalVariable : String = "Initial Value"
{
didSet { globalVariableDidSets.notifyDidSet(oldValue) }
}
// In your view controllers (or any other class), you need to setup the
// reaction to the global variable changes by registering to the observer list
//
class MyVC:UIViewController
{
override func viewDidLoad()
{
globalVariableDidSets.register(self){ $0.handleVariableChange }
// ...
}
func handleVariableChange(_ oldValue:String)
{
//...
}
}

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