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