CFNotificationCenter

CFNotificationCenter 是NSNotificationCenter 在Core Foundation 中的C 实现,一般来说,如果我们有比较高阶的API 可以使用的话,我们会尽量避免使用比较低阶的API,所以,只要有NSNotificationCenter 可以使用的场合,我们应该不会用到CFNotificationCenter。

比较有可能用到CFNotificationCenter的场合,大概是iOS 8之后,Hosting App与Extension之间的沟通。iOS 8之后推出了Extension,可以允许开发者撰写Today Widget、Share Widget以及模拟键盘等功能,Applw Watch上的Watch App也属于Extension;每个Extension都是额外可以让操作系统载入的Bundle,Extension与我们原本的App(便是Hosting App)之间,可以用Shared Data共用资料,但是当App发生改变要通知Extension,却没有什么比较直接的办法。在苹果有新的API之前,我们就会倚赖透过CFNotificationCenterGetDarwinNotifyCenter()取得的darwin notification center发送通知。

而即使我们可以发送通知,CFNotificationCenter 用起来也不是很方便,主要原因是CFNotificationCenter 不像NSNotificationCenter,在传递通知的时候可以夹带user info。再来,就是像前面说的, CFNotificationCenter 是C API,而我们会尽量希望使用比较高阶的API。

所以,KKBOX 在开发Watch App 的时候,就在CFNotificationCenter 上面又简单架构了一层Objective-C API,介面类似NSNotificationCenter,叫做KKWatchAppNotificationCenter。程式码如下:

KKWatchAppNotificationCenter.h

@import Foundation;

@interface KKWatchAppNotificationCenter : NSObject
+ (instancetype)sharedCenter;
- (void)postNotification:(NSString *)key;
- (void)addTarget:(id)target selector:(SEL)selector name:(NSString *)notification;
- (void)removeObserver:(NSObject *)observer;
@end

KKWatchAppNotificationCenter.m

#import "KKWatchAppNotificationCenter.h"

#define LFSuppressPerformSelectorLeakWarning(doPerformSelector) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
doPerformSelector; \
_Pragma("clang diagnostic pop") \
} while (0)

@interface KKWatchAppNotificationCenter ()
{
    NSMutableDictionary *notificationKeys;
}
@end

@implementation KKWatchAppNotificationCenter

+ (instancetype)sharedCenter
{
    static KKWatchAppNotificationCenter *sharedRegister = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedRegister = [[KKWatchAppNotificationCenter alloc] init];
    });
    return sharedRegister;
}

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

- (void)postNotification:(NSString *)key
{
    CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
    CFDictionaryRef const userInfo = NULL;
    BOOL const deliverImmediately = YES;
    CFNotificationCenterPostNotification(center,
        (CFStringRef)key, NULL, userInfo, deliverImmediately);
}

- (void)addTarget:(id)target selector:(SEL)selector name:(NSString *)notification
{
    if (!target) {
        return;
    }

    if (!selector) {
        return;
    }

    if (!notification || ![notification length]) {
        return;
    }

    NSMutableArray *a = [notificationKeys objectForKey:notification];
    BOOL needRegisterNotification = NO;

    if (!a) {
        a = [NSMutableArray array];
        needRegisterNotification = YES;
    }

    for (NSDictionary *d in a) {
      if (d[@"target"] == target &&
        NSSelectorFromString(d[@"selector"]) == selector) {
            return;
        }
    }

    NSDictionary *d = @{@"target": target, @"selector": NSStringFromSelector(selector)};
    [a addObject:d];

    if (needRegisterNotification) {
        [self registerNotification:notification];
        [notificationKeys setObject:a forKey:notification];
    }
}

- (void)removeObserver:(NSObject *)observer
{
    NSMutableArray *notificationsToDelete = [[NSMutableArray alloc] init];
    for (NSString *notification in notificationKeys) {
        NSMutableArray *a = notificationKeys[notification];
        NSMutableArray *objectsToDelete = [NSMutableArray array];
        for (NSDictionary *d in a) {
            id target = d[@"target"];
            if (target == observer) {
                [objectsToDelete addObject:d];
            }
        }
        [a removeObjectsInArray:objectsToDelete];
        if (![a count]) {
            [notificationsToDelete addObject:notification];
        }
    }

    for (NSString *notification in notificationsToDelete) {
        [self unregisterNotification:notification];
        [notificationKeys removeObjectForKey:notificationKeys];
    }
}

- (void)registerNotification:(NSString *)key
{
    CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
    CFNotificationSuspensionBehavior const suspensionBehavior = CFNotificationSuspensionBehaviorDeliverImmediately;
    CFNotificationCenterAddObserver(center,
        (__bridge const void *)(self),
        KKWatchAppNotificationRegisterCallback,
        (CFStringRef)key, NULL, suspensionBehavior);
}

- (void)unregisterNotification:(NSString *)key
{
    CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
    CFNotificationCenterRemoveObserver(center,
      (__bridge const void *)(self),
      (CFStringRef)key, NULL);
}

void KKWatchAppNotificationRegisterCallback(CFNotificationCenterRef center,
  void * observer, CFStringRef name, void const * object, CFDictionaryRef userInfo)
{
    KKWatchAppNotificationCenter *self = (__bridge KKWatchAppNotificationCenter *)observer;
    NSString *notification = (__bridge NSString *)name;

    NSArray *a = [self->notificationKeys objectForKey:notification];
    for (NSDictionary *d in a) {
        id target = d[@"target"];
        SEL action = NSSelectorFromString(d[@"selector"]);
        LFSuppressPerformSelectorLeakWarning([target performSelector:action withObject:nil]);
    }
}

@end

results matching ""

    No results matching ""