How to properly use CFNotificationCenterAddObserver in Swift for iOS
Asked Answered
C

2

14

Pulling my hair out getting CFNotificationCenterAddObserver to work in Swift.

        CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
        UnsafePointer<Void>(self),
        iosLocked,
        "com.apple.springboard.lockcomputer" as CFString,
        nil,
        CFNotificationSuspensionBehavior.DeliverImmediately)

The iOS docs have it listed and I have tried countless iterations on the callback and the unsafe pointer with no success.

The above function call results in this error message, which seems to be the correct init:

Cannot invoke 'init' with an argument list of type '(CFNotificationCenter!, $T4, () -> (), CFString, NilLiteralConvertible, CFNotificationSuspensionBehavior)'

I also tried bridging to objc as this post here suggests, but without success.

Here is my bridge:

LockNotifierCallback.h:

#import <Foundation/Foundation.h>

@interface LockNotifierCallback : NSObject

+ (void(*)(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo))notifierProc;

@end

and LockNotifierCallback.m:

#import "LockNotifierCallback.h"

static void lockcompleteChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    NSLog(@"success");
}

@implementation LockNotifierCallback


+ (void(*)(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo))notifierProc {
    return lockcompleteChanged;
}

@end

with updated CFNotificationCenterAddObserver call as follows:

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
        LockNotifierCallback.notifierProc,
        iosLocked,
        "com.apple.springboard.lockcomputer" as CFString,
        nil,
        CFNotificationSuspensionBehavior.DeliverImmediately)

and of course LockNotifierCallback.h is in my Bridging header. Error continues:

Cannot convert the expression's type '(CFNotificationCenter!, () -> CFunctionPointer<((CFNotificationCenter!, UnsafeMutablePointer<Void>, CFString!, UnsafePointer<Void>, CFDictionary!) -> Void)>, () -> (), CFString, NilLiteralConvertible, CFNotificationSuspensionBehavior)' to type 'StringLiteralConvertible'
Chromatics answered 29/10, 2014 at 17:31 Comment(9)
CFNotificationCenterAddObserver takes a CFunctionPointer which you can't really create from Swift (see https://mcmap.net/q/715621/-using-swift-cfunctionpointer-to-pass-a-callback-to-coremidi-api). You can work around it by doing some Objective-C bridging similar to this: https://mcmap.net/q/753032/-objective-c-wrapper-for-cfunctionpointer-to-a-swift-closureWakerly
Thanks Mark, but why would it be under a swift declaration in the ios docs? developer.apple.com/Library/ios/documentation/CoreFoundation/…Chromatics
You can still use it from Swift, you just have to pass it a function that's defined in C or Objective-C and not a Swift function of closure.Wakerly
Thanks again Mark. I'm still stuck, see edited Q above.Chromatics
I'm not sure what iosLocked is, but it looks like you just have your parameters in the wrong places. This compiles for me: CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), nil, LockNotifierCallback.notifierProc(), "com.apple.springboard.lockcomputer", nil, CFNotificationSuspensionBehavior.DeliverImmediately)Wakerly
I can confirm that Mike's last comment works along with your LockNotifierCallback bridge object. Just gave it a shot and I get a success message when locking the device, and no message when hitting home.Approach
Yes, agreed -- worked for me. Thanks Mike. Should I answer my own question here to approve it?Chromatics
You should answer your own question since you have the solution.Colquitt
@Chromatics did you find a better approach into this?Khoury
A
12

I had some issues with DarwinNotifications, you can try using this wrapper class just include header file in your bridging file. And you can use it in swift.

DarwinNotificationsManager.h :

#import <Foundation/Foundation.h>

#ifndef DarwinNotifications_h
#define DarwinNotifications_h

@interface DarwinNotificationsManager : NSObject

@property (strong, nonatomic) id someProperty;

+ (instancetype)sharedInstance;

- (void)registerForNotificationName:(NSString *)name callback:(void (^)(void))callback;
- (void)postNotificationWithName:(NSString *)name;

@end

#endif

DarwinNotificationsManager.m :

#import <Foundation/Foundation.h>
#import "DarwinNotificationsManager.h"


@implementation DarwinNotificationsManager {
    NSMutableDictionary * handlers;
}

+ (instancetype)sharedInstance {
    static id instance = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        handlers = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)registerForNotificationName:(NSString *)name callback:(void (^)(void))callback {
    handlers[name] = callback;
    CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter();
    CFNotificationCenterAddObserver(center, (__bridge const void *)(self), defaultNotificationCallback, (__bridge CFStringRef)name, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
}

- (void)postNotificationWithName:(NSString *)name {
    CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter();
    CFNotificationCenterPostNotification(center, (__bridge CFStringRef)name, NULL, NULL, YES);
}

- (void)notificationCallbackReceivedWithName:(NSString *)name {
    void (^callback)(void) = handlers[name];
    callback();
}

void defaultNotificationCallback (CFNotificationCenterRef center,
                 void *observer,
                 CFStringRef name,
                 const void *object,
                 CFDictionaryRef userInfo)
{
    NSLog(@"name: %@", name);
    NSLog(@"userinfo: %@", userInfo);

    NSString *identifier = (__bridge NSString *)name;
    [[DarwinNotificationsManager sharedInstance] notificationCallbackReceivedWithName:identifier];
}


- (void)dealloc {
    CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter();
    CFNotificationCenterRemoveEveryObserver(center, (__bridge const void *)(self));
}


@end

In swift you can use it like this :

let darwinNotificationCenter = DarwinNotificationsManager.sharedInstance()
darwinNotificationCenter.registerForNotificationName("YourNotificationName"){
            //code to execute on notification
}
Anthonyanthophore answered 13/6, 2015 at 13:37 Comment(2)
if background app refresh is enabled, then, on receipt of a darwin notification does the app wake up from the background to perform some code like it would with remote notifications?Benedikt
What is 'YourNotificationName' for hold power buttonSixteenth
C
5

I wrote this to pass a notification from a Share Extension to it's parent App, when in iPadOS, both could be active simultaneously.

These are placed in a library shared by both the App and the Extension. The App uses the ExtensionListener, the Extension uses the ExtensionEvent.

final public class ExtensionListener: NSObject {

    // the inter-process NotificationCenter
    private let center = CFNotificationCenterGetDarwinNotifyCenter()
    private var listenersStarted = false
    fileprivate static let notificationName = "com.example.CrossProcessExtensionAction" as CFString

    public override init() {
        super.init()
        // listen for an action in the Share Extension
        startListeners()
    }

    deinit {
        // don't listen anymore
        stopListeners()
    }

    //    MARK: listening
    fileprivate func startListeners() {
        if !listenersStarted {
            self.listenersStarted = true
            CFNotificationCenterAddObserver(center, Unmanaged.passRetained(self).toOpaque(), { (center, observer, name, object, userInfo) in
                // send the equivalent internal notification
                NotificationCenter.default.post(name: NSNotification.Name.SomeInternalExtensionAction, object: nil)
            }, Self.notificationName, nil, .deliverImmediately)
        }
    }

    fileprivate func stopListeners() {
        if listenersStarted {
            CFNotificationCenterRemoveEveryObserver(center, Unmanaged.passRetained(self).toOpaque())
            listenersStarted = false
        }
    }
}

final public class ExtensionEvent: NSObject {
    public static func post() {
        CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFNotificationName(rawValue: ExtensionListener.notificationName), nil, nil, true)
    }
}
Creath answered 1/10, 2019 at 16:30 Comment(2)
Awesome. How to past String message in CFNotificationCenterPostNotification?Tetrapod
My question closed, see https://mcmap.net/q/830360/-how-to-allocate-gt-send-gt-receive-gt-cast-gt-deallocate-unsaferawpointer-from-extension-to-app/4067700Tetrapod

© 2022 - 2024 — McMap. All rights reserved.