[译]AppExtension编程指南:扩展基础3

1,536 阅读17分钟

App Extension Programming Guide-App Extension Essentials AppExtension编程指南:扩展基础3

Creating an App Extension 创建应用扩展

iOS8/OS X v10.10

翻译自苹果官方文档 App Extension Programming Guide--App Extensions Essentials

创建应用扩展

iOS8/OS X v10.10

当你准备好开发一个应用扩展时,可以从选择一个支持你想执行的任务的扩展点开始。然后选择适用于你所选的扩展点的Xcode模板,如果需要的话,可使用自定义代码和自定义用户界面来增强默认文件。最后,在你调试并优化你的应用扩展之后,就可以打包进你的应用程序给用户使用了。

从选择正确的扩展点开始

每个扩展点都针对一个定义明确的用户场景,你首先要清楚这个应用能为用户提供什么样的功能,所以要选择一个支持该功能的扩展点。这是非常关键的,因为每个扩展点定义了不同的API,为你提供不同的功能开发,所以如果选错了扩展点,那么很有可能无法实现想要的功能。表1-1列出了iOS和OS X中支持的所有扩展点。

选定了一个适用于应用扩展的扩展点后,在你的containing app中会有一个新的Target。添加应用扩展Target最简单的方式就是使用Xcode提供的模板,这些模板为扩展点提供了预配置。

你可以通过菜单选择 File > New > Target在你的Xcode工程中添加一个新Target。在左边的侧边栏中,选择iOS或OS X的Application Extension选项,然后在右边面板呈现的Xcode 模板中选择你想要创建的应用扩展模板,如下图所示:

app extension templates

选择好模板,并在工程中添加Target,在自定义扩展代码之前,你可以试着编译并运行一下项目。如果你是基于Xcode提供的模板创建的扩展,那么当编译成功后,就会生成一个扩展名为 .appex的应用扩展包

注意64位的架构: 一个应用程序扩展在Architectures设置中必须要包含arm64的架构,否则在上传App Store时会被拒绝。创建完一个应用扩展后,你可以在Xcode的“Standard architectures”设置选项中设置arm64架构。

如果你的包含扩展的应用程序使用了一些嵌入框架,那么你的应用程序也必须要包含arm64架构,否则上传App Store时也会被拒绝。

关于64位架构开发环境的更多信息,请根据你的target平台参阅64-Bit Transition Guide for Cocoa Touch或者64-Bit Transition Guide for Cocoa。

在大多数情况下,你可以通过在System Preferences或Settings中启用应用扩展或者授予权限来测试默认的应用扩展,然后可以通过其他应用来访问它。比如你可以在OS X系统中通过Safari中打开一个页面来测试分享扩展,点击分享按钮,然后选择你要测试的扩展即可。

检查默认的扩展模板

一般情况下,每个扩展模板都包含一个属性列表文件(就是Info.plist文件),一个View Controller类和一个默认的UI,这些都是扩展点定义的。默认的View Controller类(或principal class)都含有扩展点对应功能的方法,需要我们去实现。

应用扩展Target的Info.plist文件除了识别扩展点外还罗列了应用扩展的详细信息。该文件至少包括NSExtension字典以及扩展点指定的其他键值字典。。比如NSExtensionPointIdentifier的key值就是扩展点的反向DNS名称,比如com.apple.widget-extension。在应用扩展的NSExtension字典中还有其他的Key和值:

NSExtensionAttributes:这是一个描述扩展点具体属性的字典,就像照片编辑扩展中的PHSupportedMediaTypes一样。

NSExtensionPrincipalClass:这是扩展模板创建的主体视图控制器类,比如SharingViewController。当载体应用程序(host app)调用扩展时,扩展点会实例化这个类。

NSExtensionMainStoryboard(只适用于iOS):扩展默认的Storyboard文件,一般名为MainInterface。

除了在属性列表中设置以外,扩展模板还可以设置一些默认的功能。每个扩展点可以定义扩展点支持的适用于某个类型任务的功能。比如,一个iOS的Document Provider扩展就包含com.apple.security.application-groups的功能。

所有的OS X扩展模板都默认包含应用程序沙箱和com.apple.security.files.user-selected.read-only功能。如果你开发的扩展需要适用网络,或者访问用户的相册,再或者需要访问用户的通讯录,那么你就需要额外定义这些功能。

注意:通常情况下,如果用户允许主应用程序(containing app)访问他们的私有数据,那么主程序里的扩展也同样拥有该权限。

响应“载体程序”(Host App)的请求

正如你在Understand How an Extension Works这篇文档中了解的,当用户在host app选择一个扩展,并使host app向扩展发出请求时,就会打开扩展。说的再详细一点,你的扩展会根据用户的操作接收到请求,帮用户执行任务,然后完成或者关闭请求。比如说,一个分享扩展收到了来自host app的请求,然后该扩展会打开相应视图来响应请求。然后用户在该界面中编辑要分享的内容,用户可以选择发送或者不发送,最后扩展根据用户的行为响应完成还是关闭请求。

当host app向扩展发出请求时,一般都会指明扩展运行的上下文。对于很多扩展来说,最重要的一部分就是要设置一个工作项,这个工作项就是用户在使用这个扩展时要处理的工作项。比如说,一个分享扩展的上下文可能就包含用户选择的想要分享的一段文字。

当host app发出一个请求(通常就是调用beginRequestWithExtensionContext:方法),你的扩展就可以用主试图控制器中的 extensionContext 属性来获得上下文,然后使用 NSExtensionContext 类解析上下文并获得工作项。通常,在视图控制器的 loadView 方法中解析上下文并获得工作项,这样在加载完视图后就可以将信息显示在视图界面中了。获取扩展上下文可以使用如下代码:

NSExtensionContext *myExtensionContext = [self extensionContext]; 

有意思的是内容对象的 inputItems 属性,它包含了应用扩展需要使用的工作项。inputItems 属性包含一个 NSExtensionItem 类型的数组,数组的每一个成员都包含一个可执行的工作项。从上下文中获取工作项可以使用如下代码:

NSArray *inputItems = [myExtensionContext inputItems]; 

每个 NSExtensionItem 对象都包含若干个描述工作项的属性,比如标题、文本内容、附件信息、用户信息。

注意 attachments 属性,它包含一个与工作项相关联的媒体数据数组。比如说一个分享请求的工作项,那么 attachments 属性可能就包含用户想要分享网页中的信息。

当用户工作项处理完后,应用扩展通常会给用户两个选择,完成任务或取消任务。根据用户的选择,扩展会调用 completeRequestReturningItems:expirationHandler:completion: 方法,把工作项返回给 host app,或者会调用 cancelRequestWithError: 方法,返回一个错误代码。

在iOS中,你的应用程序扩展可能需要更多的时间去处理潜在的需长时间处理的任务,比如说往网上上传内容。这种情况下,你就要使用 NSURLSession 类将该任务转为后台处理的任务。因为转换到后台处理任务需要用一个单独的线程,所以在扩展完成主应用请求并关闭后仍然可以处理。想了解更多关于扩展中NSURLSession类的用法,请参阅:Performing Uploads and Downloads。

重要:虽然你可以设置一个后台URL来上传或下载任务,但是有一些类型的后台任务,比如支持 VoIp 或者在后台播放音乐的任务,是不能通过扩展来实现的。

如果你应用扩展的Info.plist文件中含有 UIBackgroundModes 关键字,那么在上传App Store时会被拒绝。(想了解更多关于 UIBackgroundModes 关键字的内容,请参阅 Information Property List Key Reference 中的 UIBackgroundModes)

优化效率和性能

应用扩展在内存使用优先级上要明显低于当前运行的应用程序。不管是 iOS 还是 OS X,系统都会毫不犹豫地终止扩展,因为用户想返回到host app中的主要目标中。但是也有一些应用扩展的内存使用优先级要高于其他扩展,比如说widgets就要求要高一些,因为它要实时的显示一些信息,因为一般用户更倾向于同时开启多个widgets。

你的应用扩展并不拥有主循环线程,你要遵循这一规则,以便让扩展在主循环线程中发挥最好的性能。比如说,如果你的应用扩展阻止了主循环线程,那么在用户使用主应用程序的过程中会造成非常糟糕的用户体验效果。 我们需要记住的一点是,GPU在系统中是一个共享的资源,所以应用扩展不会得到很高的优先级照顾。比如说,如果你正在玩一个对GPU消耗很高的游戏,那么由于内存压力比较大,它就有可能会选择关闭Today widget。

设计一个精简的用户界面

大多数的扩展点都要求你向用户提供一些自定义的界面,它在用户打开你的应用扩展时呈现给用户。通常情况下,应用扩展的界面要尽可能的简约、内敛,并主要关注一个单一任务。为了提高性能和用户体验效果,你要避免与该扩展功能无关的界面出现。

大多数Xcode 提供的应用扩展模板都包含一个初始界面文件,你可以从这个文件中设计界面开始。

在用户的惯性思维中,一般他们都是通过应用扩展的图标来辨识扩展功能的。通常情况下,应用扩展的图标和它的主体应用的图标是一致的。使用主体应用的图标作为应用扩展的图标有利于用户去判断这个扩展的来源,也就是说让用户确信这个扩展是来源于他们安装的主体应用。当然也有一些例外。

在iOS中,自定义的Action扩展的图标使用其主体应用的图标。 在OS X中,如果一个扩展的主体程序只是用来安装扩展的封装包,那么该扩展要提供一个单独的图标,否则都会使用主体应用的图标。

应用扩展要使用一个简短,语义明确的名字,这能让帮助用户把扩展和你的主应用程序联系起来,并且能让他们在系统中更好的管理应用扩展。通过应用扩展 TargetCFBundleDisplayName 属性来设置它的名称,你可以在Info.plist文件中修改它。如果你没有给 CFBundleDisplayName设置值,也就是没有给扩展设置名称,那么应用扩展会使用其主体应用的名称,也就是CFBundleName属性中的值。

同时一些应用扩展也需要一个简短的说明。比如说,OS X中的 Widget 扩展就会显示一个简单的描述,这能帮助用户更好的选择他们想要显示在今日通知中的Widget扩展。扩展的描述可以在 InfoPlist.strings 文件的widget.description 属性中设置。

确保您的iOS App扩展适用于所有设备

您必须确保提交的应用扩展程序是通用的:它必须适用于iPhone,iPod touch和iPad。无论您为包含的应用选择哪个目标设备系列,此要求均适用。Xcode中的应用程序扩展模板已针对通用目标设备系列进行了正确配置。

要声明您的应用扩展程序是通用的,请使用Xcode中的目标设备系列构建设置,指定“iPhone / iPad”值。

确保您的应用扩展程序具有通用性

  1. 在keyboard project的Xcode项目导航器中,选择项目文件。 如果隐藏了项目编辑器中的project和targets列表,请显示它。为此,请单击project编辑器选项卡栏左侧的按钮。
  2. 在project 和 targets列表的targets组中,选择应用扩展程序的targets。
  3. 在project编辑器中选择Build Settings选项卡。 确保选中Basic 和 Combined按钮,以便于您更轻松地找到所需的设置。
  4. 在project编辑器的Deployment group中,查看“Targeted Device Family”设置。对于Debug和Release配置,值应为“iPhone / iPad”。 如果您找到不同的值,请将其更正为“iPhone / iPad”。

在设计和构建应用扩展时 使用Auto Layout和size classes类。测试您的应用扩展程序,以确保其符合您对所有设备大小和方向的预期行为。如“ Simulator User Guide所述,在iOS模拟器中执行此操作,如果可能,还可以在两个方向上对物理设备进行测试。

请记住,即使您的主体应用(containing app)仅针对iPad设备系列,您所包含的应用扩展程序也会以兼容模式运行显示在的iPhone应用中。

重要 要通过App Review,您必须将“iPhone / iPad”(有时称为 universal) )指定为应用扩展程序的目标设备系列,无论您为主体应用(containing app)选择哪个目标设备系列。

在以后的iOS更新中,应用扩展程序仅在扩展程序主体应用本机支持的设备(或设备兼容模式)上运行。例如,在兼容模式下使用iPhone应用程序时,在只有iPad的主体应用(containing app)提供的扩展程序将不可见。为确保获得最佳用户体验,我们建议您的主体应用(containing app)和其应用扩展程序是通用的。

调试,配置和测试你的应用扩展

注意:要确保主体应用中的所有扩展都要使用相同签名方式的代码。 Xcode项目中的所有target都必须以相同的方式进行代码签名。例如,在测试期间,您可以使用临时代码签名或使用开发人员证书,但必须对项目中的所有target使用相同的方法。要提交到App Store,请使用您的分发证书来获取所有目标。

使用 Xcode 调试应用扩展和调试其他程序基本是一样的,但唯一点不同的是:你要选择一个能访问扩展的载体应用。当你编译运行应用扩展后,Xcode 会运行载体应用,等待你去使用扩展并触发调试点来调试扩展。你要在 scheme 中要为扩展指定一个载体应用(一个 scheme 封装了 Target 编译的说明)。

当你在主体应用工程中添加一个应用扩展的Target时,Xcode 就会为应用扩展默认创建一个 scheme。应用扩展的 scheme 可以让你指定在调试时由哪个应用程序来调用你的扩展,也就是指定一个调试时的载体应用。默认情况下,当你编译运行扩展时,会询问你使用哪个载体应用来调用该扩展。

在你编译运行应用扩展之前,你要确保你的扩展已经选择了一个 scheme。你可以通过 Product > Scheme > MyExtensionName 或者使用 Xcode 菜单栏呼出 scheme 菜单并选择 MyExtensionName 来设置应用扩展的 scheme。

注意:如果你运行主体应用的 scheme 代替应用扩展的 scheme,那么你在编译工程时Xcode会告诉你它正在等待调试应用扩展。

当你编译运行应用扩展时,Xcode会为你列出允许调用该扩展的载体应用程序。当你选择一个载体应用程序并且运行后,调试器就准备开始工作了,并准备好在你打的断点处进行拦截。当你在载体应用程序中使用扩展时,就可以对应用扩展进行Debug调试了。调试应用扩展的方式和使用Xcode调试其他进程一样。

在OS X中,你在载体应用程序中访问扩展之前,要确保该扩展是允许被使用的。一般情况下,在System Preferences的扩展面板中开启或关闭扩展(你也可以在共享或Action菜单中打开应用扩展面板)。这里要注意一点,在 OS X 中使用 Widget 模拟器调试 Widget扩展时,是不需要对其进行开启操作的。当你要调试键盘扩展时,必须要开启该扩展(你可以通过Settings > General > Keyboard > Keyboards开启键盘扩展)。

在调试时,Xcode会在OS X中创建一个持续的编译应用扩展的会话。这意味着,如果你要使用OS X系统下的扩展,你需要使用Finder把它从构建处拷贝到类似 Applications folder的地方。

注意:在Xcode的调试控制台日志中,应用扩展的二进制值可能是和 CFBundleIdentifier 属性关联,而不是 CFBundleDisplayName 属性。

由于应用扩展必须具有响应性和高效性,因此当运行应用扩展时,最好在调试导航器中查看调试指标( the debug gauges)。调试指标显示扩展在运行时如何使用CPU,内存和其他系统资源。当你发现类似占用CPU资源出现异常的性能问题时,例如CPU使用率出现异常高峰,您可以使用Instruments来分析您的扩展,并确定需要改进的地方。通过在任何调试仪表报告中单击Instruments中的配置文件,您可以在调试会话期间打开Instruments(要查看调试仪报告,请单击调试区域中的仪表)。想学习了解调试监控器,请查阅Debug Your App;想学习了解Instruments,请查阅Instruments User Guide。

注意:在Xcode中选择 Product > Profile可以直接在Instruments中编译并运行应用扩展。Instruments使用方案的Profile部分中的可执行文件集作为扩展的载体。

如果要使用Xcode提供的测试框架(比如XCTest APIs)测试应用扩展,你需要在主体应用程序中写一些测试用例代码。想了解更多XCTest的知识,请参阅Testing with Xcode。

分发扩展主体应用程序

你无法直接将应用扩展上传至App Store,除非它包含在主体应用程序中,并且你不能将应用扩展从一个应用程序中转到另一个应用程序。

如果想让用户使用你的应用扩展,你必须提交一个主体应用程序到App Store中,并且主体应用程序如要有其他的功能,不能只包含应用扩展。

如果你想提交 OS X 应用程序扩展,推荐你将主体应用程序提交至App Store,但这也不是唯一的途径。在OS X中,主体应用程序就可以只包含应用扩展,而不需要提供扩展外的其他功能。

注意:如果你不使用App Store来将主体应用程序和OS X 扩展交付给用户,那么在主体应用程序通过审核前,Gatekeeper是不会允许应用扩展生效的。同时,如果你不将主体应用程序上传至App Store,那么该主体应用程序也不能签署你的开发者ID名称,所以用户必须明确从主体应用程序中重载Gatekeeper,才可以让应用扩展生效。