Skip to content

Commit

Permalink
Fix deadlock on main thread
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlokhorst committed Dec 13, 2023
1 parent 2504e74 commit 79d5acf
Showing 1 changed file with 13 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class DarwinNotificationCenter {
public func addObserver(name: String, callback: @escaping () -> Void) -> DarwinNotificationObservation {
let observation = DarwinNotificationObservation(callback: callback)
let pointer = UnsafeRawPointer(Unmanaged.passUnretained(observation).toOpaque())
let pointer = UnsafeRawPointer(Unmanaged.passUnretained(observation.closure).toOpaque())
CFNotificationCenterAddObserver(center, pointer, notificationCallback, name as CFString, nil, .deliverImmediately)
Expand All @@ -50,29 +50,35 @@ When a notification comes in, the `notificationCallback` function is called:
private func notificationCallback(center: CFNotificationCenter?, observation: UnsafeMutableRawPointer?, name: CFNotificationName?, object _: UnsafeRawPointer?, userInfo _: CFDictionary?) {
guard let pointer = observation else { return }
let observation = Unmanaged<DarwinNotificationObservation>.fromOpaque(pointer).takeUnretainedValue()
let closure = Unmanaged<DarwinNotificationObservation.Closure>.fromOpaque(pointer).takeUnretainedValue()
observation.callback()
closure.invoke()
}
```

This function must be a C-style callback function, it cannot be a closure. We're previously discussed this pattern in our post [“Working with C callback functions in Swift using parameter packs”](/blog/2023/working-with-c-callback-functions-in-swift/). We pass along a pointer to our `DarwinNotificationObservation` object, which does contain the actual closure.

```
public final class DarwinNotificationObservation: Cancellable {
fileprivate let callback: () -> Void
fileprivate class Closure {
let invoke: () -> Void
init(callback: @escaping () -> Void) {
self.invoke = callback
}
}
fileprivate let closure: Closure
fileprivate init(callback: @escaping () -> Void) {
self.callback = callback
self.closure = Closure(callback: callback)
}
deinit {
cancel()
}
public func cancel() {
DispatchQueue.main.sync {
let pointer = UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
DispatchQueue.main.async { [closure] in
let pointer = UnsafeRawPointer(Unmanaged.passUnretained(closure).toOpaque())
CFNotificationCenterRemoveObserver(center, pointer, nil, nil)
}
}
Expand Down

0 comments on commit 79d5acf

Please sign in to comment.