Swift 6 suitable notification observers in iOS

I have a couple of side projects going on, although it is always a challenge to find time of them. One of them, SignalPath, is what I created back in 2015. Currently, I have been spending some time to bump the Swift version to 6 which brought a quite a list of errors. In many places I had code what dealt with observing multiple notifications, but of course Swift 6 was not happy about it.


let handler: (Notification) -> Void = { [weak self] notification in
self?.keyboardInfo = Info(notification: notification)
}
let names: [Notification.Name] = [
UIResponder.keyboardWillShowNotification,
UIResponder.keyboardWillHideNotification,
UIResponder.keyboardWillChangeFrameNotification
]
observers = names.map({ name -> NSObjectProtocol in
return NotificationCenter.default.addObserver(forName: name,
object: nil,
queue: .main,
using: handler)
// Converting non-sendable function value to ‘@Sendable (Notification) -> Void’ may introduce data races
})

view raw

Observer.swift

hosted with ❤ by GitHub

After moving all of the notification observing to publishers instead, I can ignore the whole sendable closure problem all together.


Publishers.Merge3(
NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification),
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
)
.map(Info.init)
.assignWeakly(to: .keyboardInfo, on: self)
.store(in: &notificationCancellables)

view raw

Observer.swift

hosted with ❤ by GitHub

Great, compiler is happy again although this code could cause trouble if any of the notifications are posted from a background thread. But since this is not a case here, I went for skipping .receive(on: DispatchQueue.main). Assign weakly is a custom operator and the implementation looks like this:


public extension Publisher where Self.Failure == Never {
func assignWeakly<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable where Root: AnyObject {
return sink { [weak object] value in
object?[keyPath: keyPath] = value
}
}
}

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.