iOS 小技能:App Extension (App Extension类型、生命周期、App Extension通信、App Extension示例)

1,934 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第30天,点击查看活动详情

前言

扩展 (Extension) 是 iOS 8 和 OSX 10.10 加入的一个非常大的功能点,开发者可以通过系统提供给我们的扩展接入点 (Extension point) 来为系统特定的服务提供某些附加的功能。

应用场景:

  1. 聚合收款app基于extension拦截消息推送实现 app处于后台/被杀死的状态仍可进行收款到账的语言播报:blog.csdn.net/z929118967/…
  2. 通过其他app如Safari、WeChat、相册app分享文本、链接、文件到我们自己开发的App, 实现文件的上传和信息分享;常用于我们从其他app上传简历到招聘app。

添加扩展的具体实现步骤:File->New->Target, Application Extension->xxxx Extension

I App Extension

1.1 App Extension类型

对于 iOS 来说,可以使用的扩展接入点有以下几个:

  • NotificationServiceExtension

iOS NotificationServiceExtension实现VoiceBroadcast【app处于后台/被杀死的状态仍可进行语言播报】iOS12.1以上在后台或者被杀死无法语音播报的解决方案

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/z929118967/… 在这里插入图片描述

  • Today 扩展 - 在下拉的通知中心的 "今天" 的面板中添加一个 widget

在这里插入图片描述

  • 分享扩展 : 使用户在不同的应用程序之间分享内容。点击分享按钮后将网站、文件或者照片通过应用分享。

在这里插入图片描述

  • 动作扩展 - 点击 Action 按钮后通过判断上下文来将内容发送到应用:动作扩展允许在Action Sheet中创建自定义动作按钮,例如允许用户为文档添加水印、向提醒事项中添加内容、将文本翻译成其他语言等。动作扩展和分享扩展一样都可以在任意的应用程序中激活使用,同样也需要开发者进行相应的设置

  • 照片编辑扩展 - 在系统的照片应用中提供照片编辑的能力:将你提供的滤镜或编辑工具嵌入到系统的照片和相机应用程序中,这样用户就可以很容易地将其应用到图像和视频中

  • 文档提供扩展 - 提供和管理文件内容:如果你的应用程序是给用户提供iOS文档的远程存储,就可以创建一个Document Provider,让用户可以直接在任何兼容的应用程序中上传和下载文档

  • 自定义键盘

提供一个可以用在所有应用的替代系统键盘的自定义键盘或输入法:自定义键盘需要用户在设置中进行配置,才能在输入文字时使用。

例子: iOS上USB Keyboard安装后,打开“设置 - 通用 - 键盘 - 键盘 - 添加新键盘“,在”第三方键盘“区域点击”USB Keyboard“。

  • Audio

通过音频单元扩展,你可以提供音频效果、声音生成器和乐器,这些可以由音频单元宿主应用程序使用,并通过应用程序商店分发。

1.2 扩展的生命周期

应用程序扩展 并不是一个独立的应用程序,它是包含在应用Bundle里一个独立的包,后缀名为.appex。包含应用程序扩展的应用程序被称为容器应用(Containing App),能够使用该扩展的应用被称为宿主应用(Host App)

  • 例子:,Safari里使用微信的扩展,将一个网页分享到微信中,则Safari就是宿主应用,微信就是容器应用。

当用户在手机中安装容器应用时,应用程序扩展也会随之一起被安装;如果容器应用被卸载,应用程序扩展也会被卸载。

宿主应用程序中定义了提供给扩展的上下文环境,并在响应用户请求时启动扩展。应用程序扩展通常在完成从宿主应用程序接收到的请求不久后终止。

扩展的生命周期和包含该扩展的容器 app (container app) 本身的生命周期是独立的,准确地说它们是两个独立的进程。

  • 扩展需要对宿主 app (host app,即调用该扩展的 app) 的请求做出响应,
  • 当然,通过进行配置和一些手段,我们可以在扩展中访问和共享一些容器 app 的资源. 在这里插入图片描述

一般来说,用户在宿主 app 中触发了该扩展后,扩展的生命周期就开始了:

比如在分享选项中选择了你的扩展,或者向通知中心中添加了你的 widget 等等。

而所有的扩展都是由 ViewController 进行定义的,在用户决定使用某个扩展时,其对应的 ViewController 就会被加载,因此你可以像在编写传统 app 的 ViewController 那样获取到诸如 viewDidLoad 这样的方法,并进行界面构建及做相应的逻辑。

扩展应该保持功能的单一专注,并且迅速处理任务,在执行完成必要的任务,或者是在后台预约完成任务后,一般需要尽快通过回调将控制权交回给宿主 app,至此生命周期结束

  • 关于应用程序扩展的生命周期,我们可简单描述如下 1)用户选择需要使用的应用程序扩展。 2)系统启动应用程序扩展。 3)运行应用程序扩展的代码。 4)系统终止应用程序扩展的运行。

II App Extension通信

应用程序扩展、容器应用和宿主应用之间是如何通信?

在这里插入图片描述 在宿主应用中打开一个应用程序扩展,宿主应用向应用程序扩展发送一个请求,即传递一些数据给应用程序扩展,应用程序扩展接收到数据后,展示应用程序扩展的界面并执行一些任务,当应用程序扩展任务完成后,将数据处理的结果返回给宿主应用。

  • 虚线部分表示应用程序扩展与容器应用之间存在有限的交互方式

系统Today视图中的小组件,可以通过调用NSExtensionContext的-openURL:completionHandler:方法使系统打开容器应用,但这个方式只限Today视图中的小组件。

对于任何应用程序扩展和它的容器应用,有一个私有的共享资源,它们都可以访问其中的文件。

在这里插入图片描述

2.1 扩展和容器应用的交互

  1. 通过App Group Identifier创建一个NSUserDefaults类的实例对象,存储键值对类型的数据 通过NSFileManager类的-containerURLForSecurityApplicationGroupIdentifier:方法获取共享资源的文件路径,然后读写相应文件。

通过开启 App Groups 和进行相应的配置来开启在两个进程间的数据共享(选中Capabilities,打开App Groups选项)。

这包括了使用 NSUserDefaults 进行小数据的共享,或者使用 NSFileCoordinator 和 NSFilePresenter 甚至是 CoreData 和 SQLite 来进行更大的文件或者是更复杂的数据交互。(添加VPN配置,Packet Tunnel Provider extension :可以利用这个扩展点来实现客户端的自定义VPN隧道协议。

  1. 自定义的 url scheme 也是从扩展向应用反馈数据和交互的渠道之一(点击跳转到APP)

  2. 可以使用 iOS 8 新引入的自制 framework 的方式来组织需要重用的代码,这样在链接 framework 后 app 和扩展就都能使用相同的代码了

2.2 进程间的实时通讯方案: local socket(解决扩展和容器应用的实时通讯问题)

blog.csdn.net/z929118967/…

在这里插入图片描述

III App Extension示例

3.1 iOS NotificationServiceExtension实现VoiceBroadcast

  • NotificationServiceExtension

iOS NotificationServiceExtension实现VoiceBroadcast【app处于后台/被杀死的状态仍可进行语言播报】iOS12.1以上在后台或者被杀死无法语音播报的解决方案

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/z929118967/… 在这里插入图片描述

3.2 Today

TodayViewController中的代码和普通应用程序中新建的视图控制器的代码基本上是相同的,只是多实现了NCWidgetProviding协议。在NCWidgetProviding协议中,有一个-widgetPerformUpdateWithCompletionHandler:方法,

我们可以在这个方法中更新小组件里的内容并重新渲染界面,当小组件完成内容更新后,需要调用相应的block,给系统返回合适的更新结果。

要实现的TodayDemo小组件功能比较简单,不需要处理界面更新的相关逻辑。

  • 共享数据

在这里插入图片描述

    NSURL *url = [[NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:kGroupIdentifier] URLByAppendingPathComponent:@"TodayResult.txt"];
    int result = [[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil] intValue];
    self.numLabel.text = [NSString stringWithFormat:@"%d", result];

3.3 共享数据(通过开启 App Groups 和进行相应的配置来开启在两个进程间的数据共享)

在这里插入图片描述 在应用程序扩展里共享数据一般有如下两种方法

在应用程序扩展里共享数据一般有如下两种方法。
·通过App Group Identifier创建一个NSUserDefaults类的实例对象,存储键值对类型的数据
通过NSFileManager类的-containerURLForSecurityApplicationGroupIdentifier:方法获取共享资源的文件路径,然后读写相应文件。

    NSURL *url = [[NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:kGroupIdentifier] URLByAppendingPathComponent:@"TodayResult.txt"];
    int result = [[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil] intValue];
    self.numLabel.text = [NSString stringWithFormat:@"%d", result];

- (void)saveToUserDefaultsWithString:(NSString *)string {
    NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kGroupIdentifier];
    [userDefaults setObject:string forKey:kTodayDemoResult];
}

- (void)saveToFileWithString:(NSString *)string {
    NSURL *url = [[NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:kGroupIdentifier] URLByAppendingPathComponent:@"TodayResult.txt"];
    [string writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil];
}