iOS中宿主APP与录屏扩展进程数据传递方式
背景
在iOS生态系统中,应用程序的功能不再局限于单一的宿主应用,而是可以通过扩展进程实现更丰富的用户体验和功能。其中一种引人注目的扩展是录屏功能,它使用户能够捕捉设备屏幕上的活动,无论是游戏过程、教育演示还是其他应用场景。
然而,实现这一功能并非只涉及宿主应用的单一努力,更需要与扩展进程之间的高效数据传递。这种数据传递是确保录屏功能顺利运作的关键,也是开发者需要深入了解和灵活应用的技术之一。
在本篇博客中,我们将深入探讨iOS宿主APP与扩展进程之间的数据传递方式,了解它们如何协同工作,以实现无缝的录屏体验。
实现方案
方案一:通知
关于Fondation框架中的NSNotificationCenter应该都不陌生,但这里要使用的是Core Fondation框架中的CFNotificationCenterRef,是NSNotificationCenter更底层的实现。使用CFNotificationCenterRef进行数据传递的前提是,主应用和扩展应用需要设置相同的APP Group.
另外需要注意的是,通知只适合传递少量数据,比如一些重连,断开,或者其它少量信息的情况,大量数据的传递不适用,和我们使用NSNotificationCenter一样,应该也没有人使用它来连续不断地传递数据。
以主应用监听扩展应用为例,主应用中需要有下面三个需要实现方法:
- 声明回调block
static NSString * const notificationName = @"notificationName";
void notificationCallback(CFNotificationCenterRef center,void * observer,CFStringRef name,void const * object,CFDictionaryRef userInfo) {NSString *identifier = (__bridge NSString *)name;NSObject *sender = (__bridge NSObject *)observer;NSDictionary *notiUserInfo = @{@"identifier":identifier};//为了简化处理过程,使用常规通知承接一下。[[NSNotificationCenter defaultCenter] postNotificationName:notificationNameobject:senderuserInfo:notiUserInfo];
}
- 注册通知
//参数1:通知中心
//参数2:接收监听的对象。
//参数3:监听的回调
//参数4:通知给观察者的名称
//参数5:对于非Darwin通知中心,要观察的对象。
//参数6:决定应用程序在后台时如何处理通知。- (void)registerNotificationsWithIdentifier:(nullable NSString *)identifier {[self unregisterNotificationsWithIdentifier:identifier];CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter();CFStringRef str = (__bridge CFStringRef)identifier;CFNotificationCenterAddObserver(center,(__bridge const void *)(self),notificationCallback,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);
}
而在使用过程中和NSNotificationCenter十分相似:
- 开始注册监听
//MARK: 注册监听插件消息通知
- (void)addUploaderEventMonitor {[self registerForNotificationsWithIdentifier:@"setupSocket"];//这里同时注册了纷发消息的通知,在宿主App中使用[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(broadcastInfo:) name:notificationName object:nil];
}
- 实现监听到通知的回调方法
- (void)broadcastInfo:(NSNotification *)noti {NSDictionary *userInfo = noti.userInfo;NSString *identifier = userInfo[@"identifier"];if ([identifier isEqualToString:@"setupSocket"]) {}
}
- dealloc方法中移除监听
- (void)removeUploaderEventMonitor {[self unregisterForNotificationsWithIdentifier:@"setupSocket"];[[NSNotificationCenter defaultCenter] removeObserver:self name:ScreenHoleNotificationName object:nil];
}- (void)dealloc{[self removeUploaderEventMonitor];
}
而扩展进程只需要负责在合适的时候发送通知:
//MARK: 发送通知
- (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);
}- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.[self sendNotificationForMessageWithIdentifier:@"setupSocket" userInfo:@{}];
}
我们可以把数据放到userInfo中传递给主应用。
方案二:KVO和KVC
iOS 7引入了应用群组功能允许创建一个共享沙盒,应用和应用扩展都可以访问它。还可以支持多个应用之间共享数据,但前提是应用必须使用相同的证书进行签名。
该方案同样也离不开应用群组。同时需要借助NSUserDefauls及KVO和KVC。
仍然是以扩展进程向主应用传递数据为例,主应用需要实现以下几个步骤:
- 创建NSUserDefaults对象,并进行监听(注意该对象需要显式的强引用)。
self.userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.m10v.hperf"];
[self.userDefaulst addObserver:self forKeyPath:@"didReceiveVideo" options:NSKeyValueObservingOptionNew context:nil];
- 实现监听的回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"didReceiveVideo"]){NSData *data = [userDefaulst objectForKey:@"didReceiveVideo”];}
}
而对于扩展进程同样也只需要负责发送数据:
- 创建NSUserDefaults对象,并使用KVC给didReceiveVideo进行赋值。
NSUserDefaults * userDefaulst = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.m10v.hperf"];
NSData *videoData = [NSData new];
[userDefaulst setObject:videoData forKey:@"didReceiveVideo"];
方案三:使用NSFileManager
该方案也需要使用到应用群组,核心思想还是通过应用群组创建共享容器来帮我祝我们在两个进程间共享数据。
以扩展进程向主APP传递数据为例,与上面方案的不同之处在于,此方案的侧重点在扩展进程。
扩展进程:
- 创建NSFileManager将数据写入文件
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"your.app.group.identifier"];
NSURL *dataURL = [containerURL URLByAppendingPathComponent:@"recordedData.bin"];// 将 CMSampleBuffer 数据写入文件
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
NSData *data = [NSData dataWithContentsOfURL:dataURL];
[data writeToURL:dataURL atomically:YES];
主应用:
- 读取共享文件中的内容
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"your.app.group.identifier"];
NSURL *dataURL = [containerURL URLByAppendingPathComponent:@"recordedData.bin"];// 从文件读取 CMSampleBuffer 数据
NSData *data = [NSData dataWithContentsOfURL:dataURL];
方案四:使用Socket
使用这种方式进行数据传递比较灵活,但实现起来也比较繁琐,首先需要两个进程之间建立好Socket链接,之后才能进行数据传递的操作。以GCDAysncSocket为例。
以扩展进程发送数据到主应用为例
主应用:
- 属性声明
#import "GCDAsyncSocket.h"
@interface ViewController ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket * socket;
@property (nonatomic, strong) NSMutableArray * socketArrayM;
//消息接口队列(串行)
@property (nonatomic, strong) dispatch_queue_t queue;
@end
- 数据初始化,创建队列,初始化socket
- (void)viewDidLoad {[super viewDidLoad];[self initData];[self setupSocket];
}- (void)initData{self.socketArrayM = [NSMutableArray array];self.queue = dispatch_queue_create("com.joyme.panghu.socket", DISPATCH_QUEUE_SERIAL);
}//MARK: 初始化socket
- (void)setupSocket{self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.queue];self.socket.IPv6Enabled = NO;NSError * error;//设置端口号[self.socket acceptOnPort:8999 error:&error];//读取数据[self.socket readDataWithTimeout:-1 tag:0];}
- 实现Socket的代理方法
//MARK: GCDAsyncSocketDelegate//链接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{[self.socketArrayM removeObject:sock];
}//读取流通道关闭
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock{[self.socketArrayM removeObject:sock];
}//接收到新的链接
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{[self.socketArrayM addObject:newSocket];[newSocket readDataWithTimeout:-1 tag:0];
}//读取到数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{NSLog(@"收到:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
扩展进程:
扩展进程同样也需要进行类似的步骤,来创建Socket以进行连接。
- 属性声明
#import "SampleHandler.h"
#import "GCDAsyncSocket.h"@interface SampleHandler ()<GCDAsyncSocketDelegate>@property (nonatomic, strong) GCDAsyncSocket * socket;
///消息接收队列
@property (nonatomic, strong) dispatch_queue_t queue;@end
- 初始化数据
(void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {[self initData];[self setupSocket];
}- (void)initData{self.queue = dispatch_queue_create("com.joyme.panghu.socket_2", DISPATCH_QUEUE_SERIAL);
}- (void)setupSocket{self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.queue];NSError * error;[self.socket connectToHost:@"127.0.0.1" onPort:8999 error:&error];[self.socket readDataWithTimeout:-1 tag:0];
}
- Socket代理方法
//MARK: GCDAsyncSocketDelegate
//链接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url{}- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{[self.socket readDataWithTimeout:-1 tag:0];[self sendReadData];
}//写入数据成功
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{}//读取到数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{}- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{}
- 发送数据
//MARK: 发送数据
- (void)sendReadData{NSString * ready = @"ready";NSData * data = [ready dataUsingEncoding:NSUTF8StringEncoding];[self.socket writeData:data withTimeout:-1 tag:0];
}
建立连接成功之后,我们会在APP接收到扩展进程传递来的数据
2023-02-10 11:49:50.143508+0800 RecordDemo[7575:1395544] 收到:ready
使用Socket与上面的方法不同之处,首先不需要创建应用群组;数据传递是双向的,建立好链接后,主APP可以向扩展进程发送数据,扩展进程也可以向宿主APP发送数据。
但上面的只是一个基础的数据传递方案,想要相对完整的建立链接流程,我们还需要考虑到端口冲突的问题。实现大量数据的传递还需要其它工具来实现比如(NTESSocketPacket,数据打包,NTESTPCircularBuffer数据解包)。
结语
当涉及主APP与扩展进程之间的数据传递方案时,我们面临着多种选择,并没有一种绝对优越的方案,只有最适合特定场景的方案。在实践中,我们可能需要结合多个方案以满足不同需求。这种灵活性使得我们能够根据具体情境选择最合适的方式,从而更好地满足项目的要求。在选择方案时,务必考虑到实际需求和性能优化,以确保系统的稳定性和效率。综上所述,灵活运用不同的数据传递方案,是构建强大应用的重要一环。
相关文章:
iOS中宿主APP与录屏扩展进程数据传递方式
背景 在iOS生态系统中,应用程序的功能不再局限于单一的宿主应用,而是可以通过扩展进程实现更丰富的用户体验和功能。其中一种引人注目的扩展是录屏功能,它使用户能够捕捉设备屏幕上的活动,无论是游戏过程、教育演示还是其他应用场…...
Windows系统下的可用RADIUS软件-[资源]
RADIUS协议相关原理介绍,可参考博客RADIUS协议原理介绍报文分析配置指导-RFC2865/RFC2866。 本文用于提供和介绍Window系统下几种可用的RADIUS软件。主要涉及软件有radius_ping(绿色免安装版)和WinRadius(绿色免安装版)…...
基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十五:基础数据模块相关功能实现
一、本章内容 本章使用已实现的公共组件实现系统管理中的基础数据中的验证码管理、消息管理等功能。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址: 基于VUE3+Layui从头搭建通用后台管理系统合集-验证码功能实现 3.2 西瓜…...
MAC苹果笔记本电脑如何彻底清理垃圾文件软件?
苹果电脑以其流畅的操作系统和卓越的性能而备受用户喜爱。然而,随着时间的推移,系统可能会积累大量垃圾文件,影响性能。本文将介绍苹果电脑怎么清理垃圾文件的各种方法,以提升系统运行效率。 CleanMyMac X是一款专业的Mac清理软件…...
【Linux C | 文件I/O】文件的打开关闭 | open、creat、colse 函数
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…...
【BEV感知】BEVFormer 融合多视角图形的空间特征和时序特征 ECCV 2022
前言 本文分享BEV感知方案中,具有代表性的方法:BEVFormer。 它基于Deformable Attention,实现了一种融合多视角相机空间特征和时序特征的端到端框架,适用于多种自动驾驶感知任务。 主要由3个关键模块组成: BEV Que…...
Amazon Toolkit — CodeWhisperer 使用
tFragment--> 官网:https://aws.amazon.com/cn/codewhisperer/?trkcndc-detail 最近学习了亚马逊云科技的 代码工具,感慨颇多。下面是安装 和使用的分享。 CodeWhisperer,亚马逊推出的实时 AI 编程助手,是一项基于机器学习…...
Flink SQL填坑记2:Flink和MySQL的Bigdata类型不同导致ClassCastException报错
最近在开发Flink SQL的时候,需要关联Kafka事实表和MySQL维表,得到的数据写入Phoenix表中,但是其中有个字段,Kafka表、MySQL表和Phoenix表都是BigData类型,但是在实现的时候却报“java.math.BigInteger cannot be cast to java.lang.Long”异常,从报错信息来看,是由于Big…...
本地MinIO存储服务如何创建Buckets并实现公网访问上传文件
文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统,它可以100%的运行在标准硬件上,即X86等…...
通过https协议访问Tomcat部署并使用Shiro认证的应用跳转登到录页时协议变为http的问题
问题描述: 在最近的一个项目中,有一个存在较久,并且只在内部城域网可访问的一个使用Shiro框架进行安全管理的Java应用,该应用部署在Tomcat服务器上。起初,应用程序可以通过HTTP协议访问,一切运行都没…...
Backend - Django 项目创建 运行
目录 一、配置环境 二、创建 Django 项目 (一)新建文件夹 (二)打开文件夹 (三)打开运行终端 (四)创建基础项目 (五)创建app 1. 安装Django …...
C# .Net学习笔记—— Expression 表达式目录树
一、什么是表达式目录树 (1)Expression我们称为是表达式树,是一种数据结构体,用于存储需要计算,运算的一种结构,这种结构可以只是存储,而不进行运算。通常表达式目录树是配合Lambda一起来使用的…...
《论文阅读28》Unsupervised 3D Shape Completion through GAN Inversion
GAN,全称GenerativeAdversarialNetworks,中文叫生成式对抗网络。顾名思义GAN分为两个模块,生成网络以及判别网络,其中 生成网络负责根据随机向量产生图片、语音等内容,产生的内容是数据集中没有见过的,也可…...
一个正则快速找到在ES中使用profile的时产生慢查询的分片
在es中使用profile分析慢查询的时候,往往因为分片过多,或者因为查询条件太复杂,分析的结果几十万行。在kibana上点半天,也找不到一个耗时长的分片。 kibana上可以通过正则来匹配。其实我们只需要匹配到耗时大于10秒的请求。 检索语…...
链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)
在上一篇文章中,我们探索了顺序表这一基础的数据结构,它提供了一种有序存储数据的方法,使得数据的访 问和操作变得更加高效。想要进一步了解,大家可以移步于上一篇文章:探索顺序表:数据结构中的秩序之美 今…...
Python tkinter控件全集之组合选择框 ttk.ComboBox
Tkinter标准库 Tkinter是Python的标准GUI库,也是最常用的Python GUI库之一,提供了丰富的组件和功能,包括窗口、按钮、标签、文本框、列表框、滚动条、画布、菜单等,方便开发者进行图形界面的开发。Tkinter库基于Tk for Unix/Wind…...
Axure之中继器的使用(交互动作reperter属性Item属性)
目录 一.中继器的基本使用 二.中继器的动作(增删改查) 2.1 新增 2.2 删除 2.3 更新行 2.4 效果展示 2.5 模糊查询 三.reperter属性 在Axure中,中继器(Repeater)是一种功能强大的组件,用于创建重复…...
数字化医疗新篇章:构建智能医保支付购药系统
在迎接数字化医疗时代的挑战和机遇中,智能医保支付购药系统的建设显得尤为重要。本文将深入介绍如何通过先进的技术实现,构建一套智能、高效的医保支付购药系统,为全面建设健康中国贡献力量。 1. 引言 随着医疗科技的飞速发展,…...
11_12-Golang中的运算符
**Golang **中的运算符 主讲教师:(大地) 合作网站:www.itying.com** **(IT 营) 我的专栏:https://www.itying.com/category-79-b0.html 1、Golang 内置的运算符 算术运算符关系运算符逻辑运…...
k8s-ingress特性 9
TLS加密 创建证书 测试访问 auth认证 创建认证文件 rewrite重定向 进入域名时,会自动重定向到hostname.html 示例: 测试 版本的升级迭代,之前利用控制器进行滚动更新,在升级过程中无法做到快速回滚 更加平滑的升级࿱…...
学长亲荐!千笔AI,毕业论文全流程神器
你是否曾为论文选题发愁,反复修改却总对表达不满意?是否在文献检索中耗费大量时间,却仍找不到合适的资料?又或者在格式调整和查重问题上焦头烂额?毕业论文的写作过程充满了挑战,而这些困难往往让许多学生感…...
Janus-Pro-7B模型部署避坑指南:解决403 Forbidden等常见网络错误
Janus-Pro-7B模型部署避坑指南:解决403 Forbidden等常见网络错误 你是不是也遇到过这种情况?好不容易把Janus-Pro-7B模型部署起来,满心欢喜地准备调用,结果浏览器或者命令行里弹出一个冷冰冰的“403 Forbidden”,瞬间…...
SWF逆向工程道德准则:JPEXS Free Flash Decompiler使用规范
SWF逆向工程道德准则:JPEXS Free Flash Decompiler使用规范 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler JPEXS Free Flash Decompiler是一款功能强大的SWF逆向工程工具&a…...
嵌入式XIP技术原理与SPI NOR Flash工程实现
1. XIP 技术原理与工程实现机制1.1 XIP 的本质定义与执行模型eXecute In Place(XIP),即“芯片内执行”,是一种嵌入式系统中程序代码直接在非易失性存储器中运行的执行模式。其核心特征在于:CPU 的取指(Inst…...
GLM-OCR作品集:多类型文档识别效果展示,精度堪比专业软件
GLM-OCR作品集:多类型文档识别效果展示,精度堪比专业软件 1. 专业级OCR能力惊艳亮相 在数字化办公时代,文档识别技术已成为提升效率的关键工具。传统OCR软件往往价格昂贵且功能单一,而GLM-OCR的出现彻底改变了这一局面。这款轻量…...
WPF中打造现代化TreeView:从基础样式到高级交互美化
1. 从零开始构建现代化TreeView样式 如果你正在开发一个需要展示层级结构的WPF应用,比如文件管理器或者系统配置面板,TreeView控件绝对是你的首选。但默认的TreeView样式实在太过简陋,灰白的背景、生硬的线条,完全不符合现代UI设计…...
数据中台数据权限体系:基于RBAC的精细控制
数据中台数据权限体系:基于RBAC的精细控制 关键词:数据中台、数据权限、RBAC、访问控制、数据安全、权限管理、数据治理 摘要:本文深入探讨了数据中台环境下基于RBAC(基于角色的访问控制)的数据权限体系设计与实现。文章首先介绍了数据中台权限管理的核心挑战,然后详细解析…...
从入门到精通:Kafka核心原理与实战避坑指南
在分布式系统中,消息队列是实现高可用、高并发、解耦的核心组件,而Kafka作为当下最流行的分布式消息队列之一,凭借高吞吐量、高可靠性、可扩展性等优势,广泛应用于日志收集、消息分发、流处理等场景。无论是后端开发、大数据处理&…...
中文理解能力测试:国产AI模型 vs 海外AI模型,这次能赢吗?
中文,作为全球最难掌握的语言之一,一直是AI模型的一道“隐形门槛”。从“意思意思”到“方便的时候方便”,从“差点没赶上”到“差点赶上了”——这些让老外崩溃的中文陷阱,恰恰是检验AI语言理解能力的试金石。2026年,…...
AnimateDiff效果提升秘籍:负面提示词实战,让动态视频更清晰
AnimateDiff效果提升秘籍:负面提示词实战,让动态视频更清晰 你是否遇到过这样的困扰:用AnimateDiff生成的视频虽然动起来了,但画面总有些"脏兮兮"的感觉?人物脸上有奇怪的纹路,背景出现莫名其妙…...
