开发应用扩展 -- iOS8/OS X v10.10应用扩展编程指南

当你准备好开发一个应用扩展时,要选择一个合适的扩展点来呈现你的应用扩展的功能。然后使用Xcode提供的各个扩展点的模板来创建Target,以便使默认文件与你自定义的代码和界面更好的结合。最后,在你调试并优化你的应用扩展之后,就可以打包进你的应用程序给用户使用了。

开发之前要选择正确的扩展点

因为每一个扩展点,它的功能定义都非常明确,对用户的使用习惯针对性很强,所以你首先应该考虑清楚的是,我要开发的这个应用扩展给用户提供什么样的功能,并且用户最习惯在哪个扩展点使用它。这是非常关键的,因为每个扩展点都有不同的API,为你提供不同的功能开发,所以如果你选错了扩展点,那么很有可能你就无法实现你想要的功能。表1-1列出了iOS和OS X中支持的所有扩展点。

当你选定了一个扩展点并创建后,在你准备包含扩展的应用程序工程中就会有一个新的Target出现。创建一个应用扩展Target最简单的方式就是使用Xcode提供的模板,因为它已经帮你将基本的配置信息都配置好了,你只需要进行功能的开发即可。

你可以通过菜单choose File > New > Target在你的Xcode工程中创建一个新Target。在打开的窗口中,选择iOS或OS X的Application Extension选项,然后选择你想要创建的应用扩展模板,如下图所示:
图片

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

注意64位的架构:
一个应用程序扩展在设置其编译架构时必须要包含arm64的架构,否则在上传App Store时会被拒绝。你创建完一个应用扩展后,可以在Xcode的Standard architectures选项中设置arm64架构。
如果你的包含扩展的应用程序使用了一些框架,那么你的应用程序也必须要包含arm64架构,否则上传App Store时也会被拒绝。
关于64位架构开发环境的更多信息,请参阅64-Bit Transition Guide for Cocoa Touch

在大多数情况下,你可以在OS X的System Preferences或iOS的Settings中开启应用扩展,或授予权限,这样就可以在其他应用中测试只含有初始默认代码的扩展了。比如说,你可以在OS X系统中通过Safari中打开一个页面来测试分享扩展,点击分享按钮,然后选择你要测试的扩展即可。

检查默认的扩展模板

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

应用扩展TargetInfo.plist文件中除了标示扩展点的信息外还挪列了应用扩展的详细信息。但至少,该文件中包含一个Key NSExtension,它的值是一个用于说明扩展点的Dictionary。比如Key NSExtensionPointIdentifier的值就代表一个扩展点的名称的引用值,比如com.apple.widget-extension。在应用扩展的NSExtension字典中还有其他的Key代表了不同的信息。

  • NSExtensionAttributes:这是一个描述扩展点具体属性的字典,就像照片编辑扩展中的PHSupportedMediaTypes一样。
  • NSExtensionPrincipalClass:这是扩展模板创建的主体视图控制器类,就像SharingViewController。当主应用程序调用扩展时,扩展点会实例化这个类。
  • NSExtensionMainStoryboard(只适用于iOS):扩展的默认Storyboard文件,一般名为MainInterface

除了在属性列表中设置以外,扩展模板还可以设置一些默认的功能。每个扩展点都可以给它们提供任务类型的场景定义功能,比如说一个iOS的Document Provider扩展就包含com.apple.security.application-groups的功能。

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

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

响应主程序的请求

正如你在Understand How an Extension Works这篇文档中了解的,当用户在主程序选择一个扩展,并使主程序向扩展发出请求时,扩展才会被打开。说的再详细一点,用户做出了一个动作或者说是任务请求,然后你的扩展收到了一个请求,这个请求是帮助用户完成他希望完成的任务,最后扩展完成并关闭这个请求。比如说,一个分享扩展收到了它主程序的请求,然后该扩展响应请求并打开它相应的界面。然后用户在该界面中编辑要分享的内容,用户可以选择发送或者不发送,最后扩展根据用户的行为响应完成还是关闭请求。

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

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

NSExtensionContext *myExtensionContext = [self extensionContext];

有意思的inputItems属性,它包含了用户在使用扩展时要处理的工作项。inputItems属性包含一个NSExtensionItem类型的数组,数组的每一个成员都包含一个可执行的工作项。从上下文中获取工作项可以使用如下代码:

NSArray *inputItems = [myExtensionContext inputItems];

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

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

当用户希望的工作项处理完后,应用扩展通常会给用户两个选择,完成任务或取消任务。根据用户的选择,扩展会调用completeRequestReturningItems:expirationHandler:completion:方法,选择返回给主体程序的工作项,或者会调用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,系统都会频繁的终止扩展,因为用户想返回到主应用程序。但是也有一些应用扩展的内存使用优先级要高于其他扩展,比如说widgets就要求要高一些,因为它要实时的显示一些信息,并且一般用户都会同时开启多个widgets

你的应用扩展并不拥有主循环线程,你要遵循这一规则,以便让扩展在主循环线程中发挥最好的性能。比如说,如果你的应用扩展阻止了主循环线程,那么在用户使用主应用程序的过程中会造成非常糟糕的用户体验效果。

我们需要明确的一点是,GPU在系统中是一个共享的资源,所以应用扩展不会得到很高的优先级照顾。比如说,如果你正在玩一个对GPU消耗很高的游戏,那么由于内存压力比较大,它就有可能会选择关闭Today widget

设计一个精简的用户界面

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

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

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

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

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

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

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

注意:要确保主体应用中的所有扩展都要使用相同签名方式的代码。

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

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

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

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

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

注意:如果你选择的载体应用程序不是扩展的scheme中指定的载体应用程序,那么当你在这个载体应用程序中访问扩展时,Debug调试器是不会起作用的。

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

在调试时,Xcode会在OS X中创建一个持续的编译应用扩展的会话。这意味着,如果你要使用OS X系统下的扩展,就要将该扩展编译打包好的程序拷贝到Finder中的Applications文件里。

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

因为应用扩展必须要做出一个有效的响应,所以当你运行应用扩展时,在调试导航中查看各种调试监控器就非常的清晰明了。这些调试监控器会告诉你当前运行的应用扩展占用了多少CPU、内存和其他系统资源。当你发现类似占用CPU资源出现异常的性能问题后,你就可以使用Instruments来分析你的应用扩展,并确定需要改进的地方。想学习了解调试监控器,请查阅Debug Your App,想学习了解Instruments,请查阅Instruments User Guide

注意:通过Product > Profile可以直接在Instruments中编译并运行应用扩展。

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

分发扩展主体应用程序

你不能直接将应用扩展上传至App Store,你只能上传应用扩展的主体应用程序,并且你不能将应用扩展从一个应用程序中转到另一个应用程序。

如果想让用户使用你的应用扩展,你就比如将该扩展的主体应用程序上传至App Store中,并且主体应用程序如要有其他的功能,不能只包含应用扩展。

虽然我们推荐将应用扩展主体应用程序上传至App Store作为用户使用应用扩展的途径,但这也不是唯一的途径。比如在OS X中,主体应用程序就可以只包含应用扩展,而不需要提供其他的功能。

注意:如果你不使用通过上传主体应用程序到App Store这种方式交付应用扩展,那么在主体应用程序被用户认可或批准前,Gatekeeper是不会允许应用扩展生效的。同时,如果你不将主体应用程序上传至App Store,那么该主体应用程序也不能签署你的开发者ID名称,所以用户需要在设置用手动将你的主体应用设置为可信任的应用程序,才可以使用你的应用扩展。

原文地址:Creating an App Extension

分享到: