通过 App Groups 实现进程间通信

2,052 阅读2分钟

前言

这篇文章是对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文件记得勾选这里

最后

太晚了,要睡觉了。