I am posing a notification and also i am getting the notification and the app works, my app would be working with or without weak self
so I am little confused about using or not using it, maybe some one could help me and show me if i need weak self
and why I need it or why i do not need it here.
This code is for macOS Storyboard Cocoa project.
import Cocoa
import SwiftUI
var appName: String = "My App Name"
class ViewController: NSViewController {
override func viewWillAppear() {
NotificationCenter.default.addObserver(forName: myNotificationName, object: nil, queue: .main) { [weak self] newNotification in
if let unwrappedNewNotification = newNotification.object as? String {
self?.titleUpdater(value: unwrappedNewNotification)
}
}
let controller = NSHostingController(rootView: ContentView())
self.view = controller.view
self.view.window?.title = appName
}
private func titleUpdater(value: String) {
appName = value
self.view.window?.title = value
}
}
struct ContentView: View {
var body: some View {
VStack {
Button("Change", action: {
postMyNotification(value: appName " updated!")
})
}
.frame(width: 400.0, height: 300.0)
}
}
let myNotificationName: Notification.Name = Notification.Name(rawValue: "myNotificationName")
func postMyNotification(value: String) {
NotificationCenter.default.post(Notification(name: myNotificationName, object: value))
}
Update:
deinit {
if let unwrappedObserver: NSObjectProtocol = observer {
print("worked for deinit!")
NotificationCenter.default.removeObserver(unwrappedObserver)
}
}
CodePudding user response:
Yes, you should use weak self
in this case, because NotificationCenter
has to hold on to the closure strongly. If you don't use weak self
, your ViewController
can never be destroyed.
But you should also ensure that your observer is destroyed when the ViewController
is destroyed. The addObserver(forName:object:queue:using:)
method returns an object which you should save in an instance property and pass to NotificationCenter.default.removeObserver
in deinit
.
class ViewController: NSViewController {
private var observer: NSObjectProtocol? = nil
deinit {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
}
override func viewWillAppear() {
observer = NotificationCenter.default.addObserver(forName: myNotificationName, object: nil, queue: .main) { [weak self] newNotification in
if let unwrappedNewNotification = newNotification.object as? String {
self?.titleUpdater(value: unwrappedNewNotification)
}
}
let controller = NSHostingController(rootView: ContentView())
self.view = controller.view
self.view.window?.title = appName
}
private func titleUpdater(value: String) {
appName = value
self.view.window?.title = value
}
}
But I would recommend using a different API so you don't have to write a deinit
at all. Use Combine instead, like this:
import Combine
import SwiftUI
class ViewController: NSViewController {
private var tickets: [AnyCancellable] = []
override func viewWillAppear() {
NotificationCenter.default.publisher(for: myNotificationName)
.receive(on: DispatchQueue.main)
.sink { [weak self] in
if let self, let newTitle = $0.object as? String {
self.titleUpdater(value: newTitle)
}
}
.store(in: &tickets)
let controller = NSHostingController(rootView: ContentView())
self.view = controller.view
self.view.window?.title = appName
}
private func titleUpdater(value: String) {
appName = value
self.view.window?.title = value
}
}
This way, you don't have to write a deinit
. When the ViewController
is destroyed, it automatically destroys its instance properties, including the tickets
array and its contents. When the ticket (AnyCancellable
) returned by sink
is destroyed, it cancels the subscription.
You might even be able to eliminate the use of self
entirely. You didn't show the declaration of appName
. If it's global, you can subscribe like this instead:
NotificationCenter.default.publisher(for: myNotificationName)
.receive(on: DispatchQueue.main)
.sink { [view] in
if let newTitle = $0.object as? String {
appName = newTitle
view.window?.title = newTitle
}
}
.store(in: &tickets)
Now the closure captures view
directly instead of capturing self
, so there's no possibility of a retain cycle.