上一篇总结了一下AppGroup相关的基础知识,通过AppGroup组即可实现App之间共享同一块存储区,以达到数据共享的目的。
在录屏直播中,我们采用AppGroup这种方式可以实现宿主App和Extention的数据传递。在App中写入数据,然后在Extention中读取数据,反之也可以。
这里主要总结一下跨进程在两个App中事件传递,从Extention发送通知到宿主App。
平时开发中发送通知我们使用NSNotificationCenter这个单例类,这个类仅限在一个App内部发送通知,如果跨进程发通知的话就不能使用它了。
CFNotificationCenterGetDarwinNotifyCenter,是CoreFundation中的一个类,它可以实现跨进程发送通知,将通知从Extention App发送到宿主App中。
如果我们的App要实现录屏直播功能,需要添加Broadcast Upload Extension,这子App是负责采集、传输数据的,创建后会自动生成一个类SampleHandler。系统将采集到的录屏数据,通过SampleHandler中的Api输出,同时有一些其他Api:
- (void)broadcastStartedWithSetupInfo:(NSDictionary *)setupInfo {
[self sendNotificationForMessageWithIdentifier:@"broadcastStartedWithSetupInfo" userInfo:setupInfo];
}
- (void)broadcastPaused {
[self sendNotificationForMessageWithIdentifier:@"broadcastPaused" userInfo:nil];
}
- (void)broadcastResumed {
[self sendNotificationForMessageWithIdentifier:@"broadcastResumed" userInfo:nil];
}
- (void)broadcastFinished {
[self sendNotificationForMessageWithIdentifier:@"broadcastFinished" userInfo:nil];
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
}
打开手机桌面,然后长按录屏,选择录屏的App为我们自己的App,这时候就开始录制了。以上的接口就会被执行。开始录屏执行broadcastStartedWithSetupInfo,暂停录制执行broadcastPaused…
如果我们要将开始、暂停、结束这些事件以消息的形式发送到宿主App中,需要使用CFNotificationCenterGetDarwinNotifyCenter。
写一个发送通知的方法:
- (void)sendNotificationForMessageWithIdentifier:(nullable NSString *)identifier userInfo:(NSDictionary *)info {
CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
CFDictionaryRef userInfo = (__bridge CFDictionaryRef)info;
BOOL const deliverImmediately = YES;
CFStringRef identifierRef = (__bridge CFStringRef)identifier;
CFNotificationCenterPostNotification(center, identifierRef, NULL, userInfo, deliverImmediately);
}
这里的identifier是发送此条通知的标识,每个通知定义一个唯一的标识,以便接收端辨认是哪一条通知。
在Extension中发送通知后,在宿主App中接收消息。
需要接收通知,首先需要注册该条通知,这里写了注册和移除注册通知的方法。这里的identifier需要和发送端保持一致。
- (void)registerForNotificationsWithIdentifier:(nullable NSString *)identifier {
[self unregisterForNotificationsWithIdentifier:identifier];
CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
CFStringRef str = (__bridge CFStringRef)identifier;
CFNotificationCenterAddObserver(center,
(__bridge const void *)(self),
MyHoleNotificationCallback,
str,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
}
- (void)unregisterForNotificationsWithIdentifier:(nullable NSString *)identifier {
CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();
CFStringRef str = (__bridge CFStringRef)identifier;
CFNotificationCenterRemoveObserver(center,
(__bridge const void *)(self),
str,
NULL);
}
这里我们需要接收多个通知消息,需要一一注册才能收到消息:
注册对应事件的通知
- (void)addUploaderEventMonitor {
NSLog(@"addUploaderEventMonitor");
[self registerForNotificationsWithIdentifier:@"broadcastStartedWithSetupInfo"];
[self registerForNotificationsWithIdentifier:@"broadcastPaused"];
[self registerForNotificationsWithIdentifier:@"broadcastResumed"];
[self registerForNotificationsWithIdentifier:@"broadcastFinished"];
[self registerForNotificationsWithIdentifier:@"processSampleBuffer"];
//这里同时注册了纷发消息的通知,在宿主App中使用
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(broadcastInfo:) name:ScreenHoleNotificationName object:nil];
}
移除注册的通知
- (void)removeUploaderEventMonitor {
NSLog(@"removeUploaderEventMonitor");
[self unregisterForNotificationsWithIdentifier:@"broadcastStartedWithSetupInfo"];
[self unregisterForNotificationsWithIdentifier:@"broadcastPaused"];
[self unregisterForNotificationsWithIdentifier:@"broadcastResumed"];
[self unregisterForNotificationsWithIdentifier:@"broadcastFinished"];
[self unregisterForNotificationsWithIdentifier:@"processSampleBuffer"];
[[NSNotificationCenter defaultCenter] removeObserver:self name:ScreenHoleNotificationName object:nil];
}
MyHoleNotificationCallback是一个回调block,当收到通知时,就会回调这个block。它的实现如下:
static NSString * const ScreenHoleNotificationName = @"ScreenHoleNotificationName";
void MyHoleNotificationCallback(CFNotificationCenterRef center,
void * observer,
CFStringRef name,
void const * object,
CFDictionaryRef userInfo) {
NSString *identifier = (__bridge NSString *)name;
NSObject *sender = (__bridge NSObject *)observer;
//NSDictionary *info = (__bridge NSDictionary *)userInfo;
NSDictionary *info = CFBridgingRelease(userInfo);
NSLog(@"userInfo %@ %@",userInfo,info);
NSDictionary *notiUserInfo = @{@"identifier":identifier};
[[NSNotificationCenter defaultCenter] postNotificationName:ScreenHoleNotificationName
object:sender
userInfo:notiUserInfo];
}
这里收到通知后,做了一步转发,将消息转发出去,在在宿主App中发送通知。
在宿主App中收到MyHoleNotificationCallback这个回调后,我们将此条通知通过NSNotificationCenter纷发出去,App中其他地方如果需要监听这些事件就可以注册ScreenHoleNotificationName接收消息了。
如果其他地方不需要接收通知消息,则收到MyHoleNotificationCallback后直接处理就行,也不必再转发了。
这样事件传递,实现了宿主App和主App之间的事件通信。
- (void)broadcastInfo:(NSNotification *)noti {
NSDictionary *userInfo = noti.userInfo;
NSString *identifier = userInfo[@"identifier"];
if ([identifier isEqualToString:@"broadcastStartedWithSetupInfo"]) {
}
if ([identifier isEqualToString:@"broadcastPaused"]) {
}
if ([identifier isEqualToString:@"broadcastResumed"]) {
}
if ([identifier isEqualToString:@"broadcastFinished"]) {
}
if ([identifier isEqualToString:@"processSampleBuffer"]) {
}
}
这块只是在录屏直播中是否有用,得根据具体的业务需求来看,有时候不需要将事件传递给宿主App,也就不用发送通知了。
不要在- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType这个方法里发通知,这几方法是采集到数据就会回调,录屏过程中一直被执行,如果一直发送通知的话,会造成主线程卡顿,因为通知是同步进行的。
在发送通知的时候,可以传递一个参数userInfo,即CFNotificationCenterPostNotification(center, identifierRef, NULL, userInfo, deliverImmediately);
这个userInfo的传值问题我没有解决,应该是可以转值的,可能是转化问题,不知道怎么弄,有知道的可以一起探讨一下,或者直接在品论了告知,谢谢先。
这里是之后补充的内容
以下是别人的处理思路,出处
App 与 Extension 的代码共用iOS 10 新增了很多种 Extension,包括本文提到的两种 Broadcast Extension。主 App 与 Extension 属于不同的两个进程,代码逻辑也是分离的,而实际情况中,主 App 与 Extension 往往会包含相同的逻辑,需要共用代码。
主 App 与 Extension 属于两个不同的 target,共用代码,有以下几种方式:
一份代码创建两个副本,分别加到 App 和 Extension 两个 target 中。这种方法简单粗暴而有效,只是,如果需要改动逻辑,则需要改两份代码,想象一下,假如这种改动很频繁,世界上又有几个程序员能受得了?
抽离公共代码,放到独立的 framework,然后两个 target 都依赖该 framework,这是标准而方便的做法。
使用 CocoaPods,只需要在 Podfile 中分别写两个 target 所依赖的 pod 即可,最简洁。
我觉得 使用 CocoaPods,只需要在 Podfile 中分别写两个 target 所依赖的 pod 即可,最简洁。
这个也是一个可考虑的思路。