How can I replace addObserver with publisher in receiving notifications from NotificationCenter

Viewed 540

Currently I am using addObserver method to receive my wished notification in my ObservableObject, from the other hand we can use publisher method to receive wished notification in a View, I like to use publisher method inside my ObservableObject instead of addObserver method, how could I do that? How can I receive/notified the published value from publisher in my class?

import SwiftUI

struct ContentView: View {
    
    @StateObject var model: Model = Model()

    var body: some View {
        
        Text("Hello, world!")
            .padding()
            .onReceive(orientationDidChangedPublisher) { _ in
                print("orientationDidChanged! from View")
            }
        
    }
}

var orientationDidChangedPublisher = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)

class Model: ObservableObject {
    
    init() { orientationDidChangeNotification() }

    private func orientationDidChangeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(readScreenInfo), name: UIDevice.orientationDidChangeNotification, object: nil) }
    @objc func readScreenInfo() { print("orientationDidChanged! from ObservableObject") }
    
}
2 Answers

You can use the same publisher(for:) inside your class:

import Combine

class Model: ObservableObject {
    
    var cancellable : AnyCancellable?
    
    init() {
        cancellable = NotificationCenter.default
            .publisher(for: UIDevice.orientationDidChangeNotification)
            .sink(receiveValue: { (notification) in
                //do something with that notification
                print("orientationDidChanged! from ObservableObject")
            })
    }
}

There are two ways. First using the Combine framework (don't forget to import Combine), and the other using the usual way.

// Without Combine:
class MyClass1 {
    let notification =  NotificationCenter.default
        .addObserver(forName: UIDevice.orientationDidChangeNotification,
                     object: nil, queue: .main) { notification in
            // Do what you want
            let orientationName = UIDevice.current.orientation.isPortrait ? "Portrait" : "Landscape"
            print("DID change rotation to " + orientationName)
        }
    
    deinit {
        NotificationCenter.default.removeObserver(notification)
    }
}

// Using Combine
class MyClass2 {
    let notification = NotificationCenter.default
        .publisher(for: UIDevice.orientationDidChangeNotification)
        .sink { notification in
            // Do What you want
            let orientationName = UIDevice.current.orientation.isPortrait ? "Portrait" : "Landscape"
            print("DID change rotation to " + orientationName)
        }
    
    deinit {
        notification.cancel()
    }
}

In deinit you should remove all the observers, like i've shown above.

EDIT:

more about deinit:

deinit is the opposite side of init. It is called whenever your instance of the class is about to be removed from the memory. If you are using the Combine way, it is fine to don't use deinit because as soon as the instance of the class is removed form the memory, the notification which is of type AnyCancelleable is removed from the memory as well, and that results in the notification being cancelled automatically. But that automatic cancellation doesnt happen when you are using the normal way, and if you dont remove the observer you added, you'll have multiple observers listening to the notification. For example if you delete the deinit in the MyClass1, you'll see that the "DID change rotation to " is typed more than once (3 times for me) when you are using the class in a SwiftUI view, because the class was initialized more thatn once before the SwiftUI view is stable.

Related