通過 App Groups 實現程式間通訊

gaozm0509發表於2020-04-06

前言

這篇文章是對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 帶上即可,一圖以蔽之(自己手繪的,字醜請見諒)

通過 App Groups 實現程式間通訊

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檔案記得勾選這裡

通過 App Groups 實現程式間通訊

最後

太晚了,要睡覺了。

相關文章