本节由CocoaChina翻译组成员DevTalking (博客 )翻译自苹果官方文档 App Extension Programming Guide--Creating an App Extension 一节,敬请勘误。欢迎加入我们的翻译小组,详情请参看:CocoaChina编辑和译者招募!
当你准备好开发一个应用扩展时,可以从选择一个支持你想执行的任务的扩展点开始。然后选择适用于你所选的扩展点的Xcode模板,如果需要的话,可使用自定义代码和自定义用户界面来增强默认文件。最后,在你调试并优化你的应用扩展之后,就可以打包进你的应用程序给用户使用了。
从选择正确的扩展点开始
每个扩展点都针对一个定义明确的用户场景,你首先要清楚这个应用能为用户提供什么样的功能,所以要选择一个支持该功能的扩展点。这是非常关键的,因为每个扩展点定义了不同的API,为你提供不同的功能开发,所以如果选错了扩展点,那么很有可能无法实现想要的功能。表1-1列出了iOS和OS X中支持的所有扩展点。
选定了一个适用于应用扩展的扩展点后,在你的containing app中会有一个新的Target。添加应用扩展Target最简单的方式就是使用Xcode提供的模板,这些模板为扩展点提供了预配置。
你可以通过菜单选择 File > New > Target在你的Xcode工程中添加一个新Target。在左边的侧边栏中,选择iOS或OS X的Application Extension选项,然后在右边面板呈现的Xcode 模板中选择你想要创建的应用扩展模板,如下图所示:
选择好模板,并在工程中添加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 key、一个key的字典以及扩展点指定的值。比如Key NSExtensionPointIdentifier的值就是扩展点的反向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中,如果一个扩展的主体程序只是用来安装扩展的封装包,那么该扩展要提供一个单独的图标,否则都会使用主体应用的图标。
应用扩展要使用一个简短,语义明确的名字,这能让帮助用户把扩展和你的主应用程序联系起来,并且能让他们在系统中更好的管理应用扩展。通过应用扩展 Target的 CFBundleDisplayName 属性来设置它的名称,你可以在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调试了。调试应用扩展的方式和使用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 属性。
因为应用扩展必须要做出一个有效的响应,所以当你运行应用扩展时,在调试导航中查看各种调试监控器就非常的清晰明了。这些调试监控器会告诉你当前运行的应用扩展占用了多少CPU、内存和其他系统资源。当你发现类似占用CPU资源出现异常的性能问题时,你就可以使用Instruments来分析你的应用扩展,并确定需要改进的地方。想学习了解调试监控器,请查阅Debug Your App;想学习了解Instruments,请查阅Instruments User Guide。
注意:在Xcode中选择 Product > Profile可以直接在Instruments中编译并运行应用扩展。Instruments使用
如果要使用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,才可以让应用扩展生效。