Consider this simple class:
import Foundation
class ExampleClass {
init() {
let notificationCenter = NotificationCenter.default
var observer: NSObjectProtocol? = nil
// A warning is emitted for the next line
observer = notificationCenter.addObserver(
forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil
) { [weak self] _ in
self?.doSomething()
notificationCenter.removeObserver(observer!)
}
}
func doSomething() {
print("We got the notification")
}
}
This code uses the exact pattern that Apple suggests in their documentation for NotificationCenter.addObserver(forName:object:queue:using:)
, where the NotificationCenter
gives us some opaque token that conforms to NSObjectProtocol
, and we later use that token to remove the observer.
Recently, though, this code has started to produce a warning. On the line where observer
is assigned, the compiler complains that
'observer' mutated after capture by sendable closure
I understand where the compiler is coming from here: if observer
is a value type, then the closure will indeed get an “old” version of it. This code does work, though, which suggests to me that either addObserver()
returns a reference type, or else that it returns a value-type handle into data that NotificationCenter
is storing itself. (It’s unfortunate that Apple doesn’t give us a more specific return type for the method.)
Does this warning indicate an actual problem in this case? If so, what’s the best alternative pattern to use?
You can store the observer on the object itself:
import Foundation
class ExampleClass {
private var observer: NSObjectProtocol?
init() {
let notificationCenter = NotificationCenter.default
observer = notificationCenter.addObserver(
forName: .NSExtensionHostDidEnterBackground,
object: nil,
queue: nil
) { [weak self] _ in
if let self {
self.doSomething()
notificationCenter.removeObserver(self.observer!)
}
}
}
func doSomething() {
print("We got the notification")
}
}
This workaround silences the warning, and it shouldn’t change the semantics of the program in a meaningful way. It’s a bit less elegant, in that a variable that could have stayed local to init
(and its closures) is now exposed to the entire class, but it should have the same effect as the previous version of the code.