前言
这篇文章是对iOS 负一屏(Today widget)功能的实现 中所提到的进程间数据通信的具体实现。
具体实现
Api 及设计思路
实现的方式是设计了一个管理数据通信的类,App 和 Widget 皆可调用,为防止多次注册,就用到了单例模式。
Api 设计灵感来源于NSNotificationCenter
,调用很简单。
/// 发送通知
/// @param type 类型 WidgetNotificationMangerType
/// @param name 通知名称
/// @param object 参数
- (void)notfyWithType:(WidgetNotificationMangerType)type name:(NSString *)name object:(id)object;
/// 注册通知
/// @param type 类型 WidgetNotificationMangerType
- (void)registerWithType:(WidgetNotificationMangerType)type;
第一个方法是发送通知,类似NSNotificationCenter
中的 - (void)postNotification:(NSNotification *)notification;
方法,第二个方法为注册,只有注册才收到跨进程的通知,在宿主 App 中可于 AppDelegate didFinishLaunchingWithOptions 中调用,在 Widget 中在 ViewDidLoad 方法中调用。
鉴于是通过通知的方式通信,所以设计了一个区分数据流向枚举 Widget→App 和 App→Widget ,如下
typedef NS_ENUM(NSUInteger, WidgetNotificationMangerType) {
WidgetNotificationMangerTypeWidgetToAppKey = 1,
WidgetNotificationMangerTypeAppToWidgetKey,
};
接收数据时,按照平时我们接受通知的方式即可。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sel:) name:@"<your.name>" object:nil];
your.name 和 notfyWithType 方法中的 name 保持一直。
实现原理
其实很简单,通过 NSUserDefaults 和 CFNotificationCenterRef 结合使用。发送通知时把要发送的内容先通过 NSUserDefaults 存储下来,发送通知和接受通知用的通知名称与数据流向相关,这里我定义了两个静态字符串。
static NSString *const kWidgetNotificationWidgetToAppKey = @"kWidgetNotificationWidgetToAppKey";
static NSString *const kWidgetNotificationAppToWidgetKey = @"kWidgetNotificationAppToWidgetKey";
存储是用到的 key 和通知(这里的通知时本地通知)的 name 相同,另一个进程收到通知后,以 name 去匹配 NSUserDefaults 中的值,然后再通过 NSNotificationCenter 发送一次通知,这次的通知 name 和上文提到的 name 也相同,这次通知把刚才从 NSUserDefaults 中取到的 value 带上即可,一图以蔽之(自己手绘的,字丑请见谅)
code
// 注册
- (void)registerWithType:(WidgetNotificationMangerType)type {
CFNotificationCenterRef notificationCenter = CFNotificationCenterGetDarwinNotifyCenter ();
CFStringRef key = (type == WidgetNotificationMangerTypeAppToWidgetKey) ? (__bridge CFStringRef)kWidgetNotificationAppToWidgetKey : (__bridge CFStringRef)kWidgetNotificationWidgetToAppKey;
// 先 remove 一次,不然那会收到多次通知
CFNotificationCenterRemoveObserver(notificationCenter, (__bridge const void *)(self), key, NULL);
CFNotificationCenterAddObserver(notificationCenter, (__bridge const void *)(self), observerMethod,key, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
}
// 发送通知
- (void)notfyWithType:(WidgetNotificationMangerType)type name:(NSString *)name object:(id)object {
NSString *cfNotifyName = kWidgetNotificationWidgetToAppKey;
if (type == WidgetNotificationMangerTypeAppToWidgetKey) {
cfNotifyName = kWidgetNotificationAppToWidgetKey;
}
NSUserDefaults *userDefaults = [[NSUserDefaults standardUserDefaults] initWithSuiteName:kSuiteName];
[userDefaults setValue:@{name:object} forKey:cfNotifyName];
[userDefaults synchronize];
[self postNotificaitonWithName:cfNotifyName];
}
- (void)postNotificaitonWithName:(NSString *)name {
CFStringRef cname = (__bridge CFStringRef)name;
CFNotificationCenterRef notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
CFNotificationCenterPostNotification(notificationCenter, cname, NULL,NULL, YES);
}
// 处理接受到的通知
void observerMethod (CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSString *notificationName = (__bridge NSString *)name;
NSUserDefaults *userDefaults = [[NSUserDefaults standardUserDefaults] initWithSuiteName:kSuiteName];
NSDictionary *dic = [userDefaults valueForKey:notificationName];
NSNotification *notification = [[NSNotification alloc] initWithName:dic.allKeys.firstObject object:dic.allValues.firstObject userInfo:nil];
[userDefaults synchronize];
[[NSNotificationCenter defaultCenter] postNotification:notification];
}
使用时还有一点要注意,.m文件记得勾选这里
最后
太晚了,要睡觉了。