前言
這篇文章是對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檔案記得勾選這裡
最後
太晚了,要睡覺了。