iOS录屏直播(四)主App和宿主App数据共享,通信功能实现

文章目录

    • CFNotificationCenterGetDarwinNotifyCenter
      • 发送通知
      • 接收通知
      • 注意事项
      • 遗留问题
      • 补充

Morris_
2019.06.17

上一篇总结了一下AppGroup相关的基础知识,通过AppGroup组即可实现App之间共享同一块存储区,以达到数据共享的目的。

在录屏直播中,我们采用AppGroup这种方式可以实现宿主App和Extention的数据传递。在App中写入数据,然后在Extention中读取数据,反之也可以。

这里主要总结一下跨进程在两个App中事件传递,从Extention发送通知到宿主App。

CFNotificationCenterGetDarwinNotifyCenter

平时开发中发送通知我们使用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 即可,最简洁。 这个也是一个可考虑的思路。

你可能感兴趣的:(#,ReplayKit,#,周报)