I can help you set up a notification handler but I must reiterate that using undocumented notification strings is a good way to:
Get rejected by App Review, perhaps now, perhaps in the future
Have your app break after some OS update
You really don’t want to do this.
I get the error: "A C function pointer cannot be formed from a closure that captures context"
By default Swift functions (including methods) use Swift calling conventions. This allows them to do clever things like capture values from the surrounding context. For example:
func startTimer(with message: String) {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
print(message)
}
}
Here we’re passing an anonymous function (a closure) to
scheduledTimer(withTimeInterval:repeats:block:)
. Note how that function ‘captures’ the
message
value from the surrounding context, so it can print that message when it runs after the timer fires.
In C, functions do not support this. C functions can only access:
Their parameters
Global variables
Swift has the ability to create C functions so that you can interoperate with C APIs, like CFNotificationCenter. When you do this you have to tell Swift to generate a C function, not a Swift function. This can happen two ways:
If you put your function inline, Swift can infer that a C function is required.
If not, you have to explicitly tell Swift that a C function is required via the
@convention(c)
syntax.
Here’s an example of those two approaches:
# first approach
CFNotificationCenterAddObserver(
center,
nil,
{ (center, observer, name, object, userInfo) in
… code elided …
},
"com.example.dts.eskimo1.MyFunkyNotification" as CFString,
nil,
.deliverImmediately
)
# second approach
let callback = { (
center: CFNotificationCenter?,
observer: UnsafeMutableRawPointer?,
name: CFNotificationName?,
object: UnsafeRawPointer?,
userInfo: CFDictionary?
) in
… code elided …
}
CFNotificationCenterAddObserver(
center,
nil,
callback,
"com.example.dts.eskimo1.MyFunkyNotification" as CFString,
nil,
.deliverImmediately
)
Note the
CFNotificationCallback
type declaration. That’s where the
@convention(c)
lives. If you leave this out, Swift won’t know that
callback
is meant to be a C function and things will fail.
IMO the first approach, putting the function inline, is easier.
The next obvious question is, how do you provide context to a C-style callback? The answer here is that C APIs typically provider a parameter you can use for this. It goes by various names, including info pointer, cookie, and, if you’re on old school Mac person, refCon. To use this you must pack all the context that your C-style callback needs into some value and pass the address of that value to the top-level context parameter. Your C-style callback will be called with that address, at which point it can unpack all of its context.
This is a PITA but that’s C for you (-;
In the case of
CFNotificationCenterAddObserver
, that context parameter is labelled
observer
. So, you must pack your context into some value and pass that to the
observer
parameter of
CFNotificationCenterAddObserver
. Then, when it calls your callback, it passes in that address via
itsobserver
parameter, allowing the callback to unpack those values.
The easiest way to do this packing is to put the context into the properties of an object and pass the address of that object in. This is what the list post I referenced is doing. In that case the object in question is
self
. It gets the address of
self
via
Unmanaged.passRetained(self).toOpaque()
. Then, in the callback, it recovers that object (which it calls
obj
to avoid confusion) via
Unmanaged<Watcher>.fromOpaque(info!).takeUnretainedValue()
, where
Watcher
is the type of
self
.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"