12 Replies
      Latest reply: Feb 6, 2017 5:12 PM by KevinE RSS
      ferasOS Level 1 Level 1 (0 points)

        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.

        • Re: Darwin Notifications in Swift
          OOPer Level 7 Level 7 (3,525 points)

          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.

            • Re: Darwin Notifications in Swift
              ferasOS Level 1 Level 1 (0 points)

              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

                • Re: Darwin Notifications in Swift
                  OOPer Level 7 Level 7 (3,525 points)

                  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.

                  • Re: Darwin Notifications in Swift
                    eskimo Apple Staff Apple Staff (7,025 points)

                    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"

                      • Re: Darwin Notifications in Swift
                        ferasOS Level 1 Level 1 (0 points)

                        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.

                          • Re: Darwin Notifications in Swift
                            eskimo Apple Staff Apple Staff (7,025 points)

                            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"

                              • Re: Darwin Notifications in Swift
                                ferasOS Level 1 Level 1 (0 points)

                                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

                                  • Re: Darwin Notifications in Swift
                                    eskimo Apple Staff Apple Staff (7,025 points)

                                    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"

                                      • Re: Darwin Notifications in Swift
                                        ferasOS Level 1 Level 1 (0 points)

                                        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

                                          • Re: Darwin Notifications in Swift
                                            eskimo Apple Staff Apple Staff (7,025 points)

                                            … 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"

                                              • Re: Darwin Notifications in Swift
                                                ferasOS Level 1 Level 1 (0 points)

                                                Thanks. i will open an incident.

                                                 

                                                Best,

                                                 

                                                Feras A.

                                                  • Re: Darwin Notifications in Swift
                                                    KevinE Apple Staff Apple Staff (15 points)

                                                    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