iOS App Extensions初识及工作原理
概念
应用扩展可让您将自定义功能和内容扩展到您的应用之外,并在用户与其他应用或系统交互时提供给用户。
您创建一个应用扩展来启用特定任务。例如,要让用户从 Web 浏览器发布到您的社交服务,您可以提供共享扩展。或者,为了让用户赶上他们最喜欢的球队,您可以提供一个 Today 小部件,在通知中心显示当前的体育比分。您甚至可以创建一个应用程序扩展,它提供用户可以用来代替 iOS 系统键盘的自定义键盘。
创建和交付应用程序扩展
应用扩展不同于应用。尽管您必须使用应用程序来包含和交付您的扩展程序,但每个扩展程序都是一个单独的二进制文件,独立于用于交付它的应用程序运行。
您可以通过向应用添加新目标来创建应用扩展。与任何目标一样,扩展目标指定组合以在您的应用程序项目中构建产品的设置和文件。您可以向单个应用添加多个扩展目标(包含一个或多个扩展的应用称为包含应用)。
开始开发应用程序扩展的最佳方法是使用 Xcode 为两个平台上的每个扩展点提供的模板之一。每个模板都包含特定于扩展点的实现文件和设置,并生成一个单独的二进制文件,该二进制文件被添加到包含应用程序的包中。
要将应用程序扩展分发给用户,您需要向 App Store 提交一个包含应用程序。当用户安装您的包含应用程序时,它包含的扩展程序也会被安装。
安装应用扩展后,用户必须采取措施启用它。通常,用户可以在其当前任务的上下文中启用扩展。例如,如果您的扩展程序是“今日”小部件,则用户可以在通知中心编辑“today”视图以启用您的扩展程序。在其他情况下,用户可以使用“设置”(在 iOS 中)或“系统偏好设置”(在 macOS 中)来启用和管理他们安装的扩展。
工作原理
应用扩展程序不是应用程序。它实现了一个特定的、范围明确的任务,该任务遵循特定扩展点定义的策略。
应用扩展的生命周期
因为应用扩展不是应用,所以它的生命周期和环境是不同的。在大多数情况下,当用户从应用程序的 UI 或呈现的活动视图控制器中选择扩展程序时,它就会启动。用户用来选择应用扩展的应用称为宿主应用。主机应用定义提供给扩展的上下文,并在它发送请求以响应用户操作时启动扩展生命周期。扩展程序通常会在完成从主机应用程序收到的请求后立即终止。
-
步骤 2 中,系统实例化宿主应用程序请求中标识的应用程序扩展,并在它们之间建立通信通道。扩展程序在宿主应用程序的上下文中显示其视图,然后使用它在宿主应用程序的请求中接收到的项目(文件)来执行其任务。
-
步骤 3 中,用户在应用扩展中执行或取消任务并解除它。响应此操作,扩展程序通过立即执行用户的任务或在必要时启动后台进程来完成主机应用程序的请求。宿主应用程序拆除扩展程序的视图,用户返回到宿主应用程序中的先前上下文。当扩展的任务完成时,无论是立即还是稍后,结果都可能返回给宿主应用程序。
-
步骤 4 ,在应用扩展执行其任务(或启动后台会话以执行它)后不久,系统终止扩展。
应用扩展如何通信
应用程序扩展主要与其宿主应用程序进行通信,如下图:
Containing app
: 容器app,即为你添加扩展的应用。App extension
: 即给应用添加的扩展。Host app
: 宿主app,即为想把文件分享给你的App的第三方app。
应用扩展与其包含的应用之间没有直接通信;通常,当包含的扩展正在运行时,包含的应用程序甚至不会运行。应用扩展的包含应用和宿主应用根本不通信。
在典型的请求/响应事务中,系统代表主机应用程序打开应用程序扩展,在主机提供的扩展上下文中传送数据。扩展显示用户界面,执行一些工作,如果适合扩展的目的,将数据返回给主机。
图 中 的虚线表示应用扩展与其包含的应用之间可用的有限交互。其中只有Today 小部件(其他应用程序扩展类型不行)可以通过NSExtensionContext
类调用的openURL:completionHandler:
方法要求系统打开其包含的应用程序。
应用扩展可以与应用间接通信
只有Today 小部件可以与应用间接通信,其他应用程序扩展类型不行。
如图中的读/写箭头所示,任何应用扩展及其包含的应用都可以访问私有定义的共享容器中的共享数据。图中以简单形式显示了扩展、其宿主应用程序及其包含应用程序之间通信的完整词汇表。
某些 API 对应用扩展不可用
应用扩展不能:
-
访问一个
sharedApplication
对象,因此不能使用该对象上的任何方法 -
使用带有
NS_EXTENSION_UNAVAILABLE
宏的头文件中标记的任何 API ,或类似的不可用宏,或不可用框架中的任何 API
例如,在 iOS 8.0 中,HealthKit 框架和 EventKit UI 框架对应用程序扩展不可用。 -
访问 iOS 设备上的摄像头或麦克风(与其他应用扩展不同,
iMessage
应用确实可以访问这些资源,只要它正确配置了NSCameraUsageDescription
和NSMicrophoneUsageDescription Info.plis
t密钥) -
执行长时间运行的后台任务
-
此限制的细节因平台而异,如本文档中的扩展点章节所述。(应用扩展可以使用
NSURLSession
对象启动上传或下载,并将这些操作的结果报告给包含的应用。) -
使用 AirDrop 接收数据(应用程序扩展可以使用 AirDrop发送数据,方式与应用程序相同:通过使用UIActivityViewController类。)
声明共享或操作扩展支持的数据类型
在您的共享或操作扩展中,您可能可以处理某些类型的数据,但不能处理其他类型的数据。为确保主机应用仅在用户选择了您支持的类型的数据时才提供您的扩展,请将NSExtensionActivationRule
密钥添加到您的扩展的Info.plist
属性列表文件中。您还可以使用此键指定您的扩展程序可以处理的每种类型的最大项目数。
当您的扩展运行时,系统会将NSExtensionActivationRule
键的值与扩展项目的attachments
属性中的信息进行比较。参考扩展键
例如,要声明您的 Share 扩展最多可以支持十张图片、一部电影和一个网页 URL,您可以使用以下字典作为NSExtensionAttributes键的值:
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
如果您不支持特定数据类型,请使用0相应键的值或从NSExtensionActivationRule字典中删除该键。
常见场景
数据共享
想要实现数据共享第一步就是App与扩展分别添加同一个App Groups
。debug的时候可以直接在应用添加,发布的时候就需要早在开发者中心该应用里添加App Groups 在添加该ID。![请添加图片描述](https://www.icode9.com/i/ll/?i=e9fd011e13e04a73b024fd9a04021b2e.png?,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAd2Foa2lt,size_20,color_FFFFFF,t_70,g_se,x_16
方式一:NSUserDefaults
NSUserDefaults:要想设置或访问Group的数据,不能在使用standardUserDefaults方法来获取一个NSUserDefaults对象了。应该使用initWithSuiteName:方法来初始化一个NSUserDefaults
对象,其中的SuiteName就是创建的Group的名字,然后利用这个对象来实现,跨应用的数据读写,代码如下:
//初始化一个供App Groups使用的NSUserDefaults对象
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.xxx.xxx"];
//写入数据
[userDefaults setValue:@"value" forKey:@"key"];
//读取数据
NSLog(@"%@", [userDefaults valueForKey:@"key"]);
方式二:NSFileManager
通过调用 containerURLForSecurityApplicationGroupIdentifier:
方法可以获得AppGroup的共享目录,然后在此目录的基础上实现任意的文件操作。
//获取分组的共享目录
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.xxx.xxx"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"xxx.txt"];
//写入文件
[@"xxx" writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
//读取文件
NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
方式三:CoreData
其实CoreData是基于NSFileManager取得共享目录后来实现数据共享的。即在初始化CoreData时,先使用NSFileManager取得共享目录,然后再指定共享目录为存储数据文件的目录(如存储的sqlite文件)。
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.xxx.xxx"];
NSURL *storeURL = [containerURL URLByAppendingPathComponent:@"DataModel.sqlite"];
//初始化持久化存储调度器
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:nil];
//创建受控对象上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context performBlockAndWait:^{
[context setPersistentStoreCoordinator:coordinator];
}];
提审AppStore的注意事项
- 扩展中的处理不能太长时间阻塞主线程(建议放入线程中处处理),否则可能导致苹果拒绝你的应用。
- 扩展不能单独提审,必须要跟容器程序一起提交AppStore进行审核。
- 提审的扩展和容器程序的Build Version要保持一致,否则在上传审核包的时候会提示警告,导致程序无法正常提审。(Info.plist : 里面的版本号必须要和主工程的版本号一致,否则审核可能被拒。)