Darwin Notifications in Swift

I am trying to observe for the Darwin notification () in swift. So far I have:


let cfstr: CFString = "com.apple.springboard.hasBlankedScreen" as NSString

let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

let function = CFNotificationCallback(backgroundBrightnessFunction)


CFNotificationCenterAddObserver(notificationCenter,

nil,

function,

cfstr,

nil,

CFNotificationSuspensionBehavior.deliverImmediately);



But the call back function field isn't allowing me to out in a normal function. How do I write a function in swift that has the CFnotificationCallback type?


Thanks,


Feras A.

Replies

I have never tried using Darwin notification, but this code compiles without errors:


let backgroundBrightnessFunction: CFNotificationCallback = {center, observer, name, object, userInfo in
    //...
}
let cfstr = "com.apple.springboard.hasBlankedScreen" as CFString
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
let function = backgroundBrightnessFunction
CFNotificationCenterAddObserver(notificationCenter,
                                nil,
                                function,
                                cfstr,
                                nil,
                                .deliverImmediately)


One thing you should know is that you cannot convert closure types using initializer notation.

Awesome Thanks. But can I run a swift function in that call back so that whenever the cfstr is observed we run a few lines of code

Why don't you try it by yourself? Without specifying what are the few lines of code, I cannot say any more.


Generally, `backgroundBrightnessFunction` in my code is just a Swift closure (of `@convection(c)`), you can write arbitrary Swift statements using the arguments.

let cfstr: CFString = "com.apple.springboard.hasBlankedScreen" as NSString

Oi vey! Please don’t do this; such notification keys are not considered API. For more details, see this post.

But can I run a swift function in that call back so that whenever the cfstr is observed we run a few lines of code

As OOPer said, you can run

@convention (C)
code just fine here. Things get more complex when you need to access your context. I recently posted an example of how you can do this to swift-users.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks. But I have to be honest I got abit lost with your code you posted to swift-users.


Basically I have two functions one to run a tap in an AVAudioEngine and the other to run the same code but with a timer so the microphone turns on and off intermittently.


Basically we want to run the TimerON code when the phone has a blanked screen and the TimerOFF code when the screen is visible.
I was expecting something like this:


let backgroundBrightnessFunction: NotificationCallback = {center, observer, name, object, userInfo in

print("hi!")

TimerON()

}


let cfstr = "com.apple.springboard.hasBlankedScreen" as CFString

let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

let function = backgroundBrightnessFunction2

CFNotificationCenterAddObserver(notificationCenter,

nil,

function,

cfstr,

nil,

.deliverImmediately)

}


But im guessing because it is not C code it will not run? I get the error: "A C function pointer cannot be formed from a closure that captures context"


Btw I started iOS development and swift about 3 months ago so Im a beginner so I apologise in advance if this is a trivial problem that I just dont understand.

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 its
observer
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"

Thank you Quinn,


That is very helpful. regarding your first point, Is there a way (that would be accepted by the App store) of running the app differently when the screen is on and when the screen is blanked?


Really Appreciate all your help?


F

Is there a way (that would be accepted by the App store) of running the app differently when the screen is on and when the screen is blanked?

Have you looked at the UIScreen class? It’s not really my area of expertise but it seems to provide info about brightness (including a notification,

UIScreenBrightnessDidChangeNotification
).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I have but it only picks up brightness level changes while the screen is on. once it is blanked it does not read it and then resets it when the screen is turned back on to the previous setting.


Thanks anyways.


Best,


F

… it only picks up brightness level changes while the screen is on.

Bummer.

This is, alas, way outside of my area of expertise. If no one else chimes in, I recommend you open a DTS tech support incident so one of our UI folks can give you a definitive answer.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks. i will open an incident.


Best,


Feras A.

Hi,


Unfortunately, DTS isn't going to be able help much here. iOS simply doesn't provide a mechanism for detecting this. There were other technique that worked in the past but historically this sort of app behavior has caused for more problems than it solved, so over time these other techniques have been removed of otherwise broken. By design, we simply don't provide an API that will allow an app to do what you're trying to do.


-Kevin

Hi Eskimo,



Can we use DarwinNotification to notify Network extension from container app & vice versa?

Does apple approve the app on appstore?



Best Regards,


iOS D

This thread has really lacks focus so, rather than make that worse, I’m going to recommend that you open a new thread over in Core OS > Networking and I’ll respond there.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"