您的位置:首页 > 移动开发 > IOS开发

iOS 通知中心扩展 制作总结与Demo(1)

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

AD:2014WOT全球软件技术峰会北京站 课程视频发布

11月21日-22日 与WOT技术大会相约深圳 现在抢票





本文是我的 WWDC 2014 笔记 中的一篇,涉及的 Session 有

Creating Extensions for iOS and OS X, Part 1

Creating Extensions for iOS and OS X, Part 2

总览

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

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

2. 分享扩展 - 点击分享按钮后将网站或者照片通过应用分享

3. 动作扩展 - 点击 Action 按钮后通过判断上下文来将内容发送到应用

4. 照片编辑扩展 - 在系统的照片应用中提供照片编辑的能力

5. 文档提供扩展 - 提供和管理文件内容

6. 自定义键盘 - 提供一个可以用在所有应用的替代系统键盘的自定义键盘或输入法

系统为我们提供的接入点虽然还比较有限,但是不少已经是在开发者和 iOS 的用户中呼声很高的了。而通过利用这些接入点提供相应的功能,也可以极大地丰富系统的功能和可用性。本文将先不失一般性地介绍一下各种扩展的共通特性,然 后再以一个实际的例子着重介绍下通知中心的 Today 扩展的开发方法,以期为 iOS 8 的扩展的学习提供一个平滑的入口。

Apple 指出,iOS 8 中开发者的中心并不应该发生改变,依然应该是围绕 app。在 app 中提供优秀交互和有用的功能,现在是,将来也会是 iOS 应用开发的核心任务。而扩展在 iOS 中是不能以单独的形式存在的,也就是说我们不能直接在 AppStore 提供一个扩展的下载,扩展一定是随着一个应用一起打包提供的。用户在安装了带有扩展的应用后,将可以在通知中心的今日界面中,或者是系统的设置中来选择开 启还是关闭你的扩展。而对于开发者来说,提供扩展的方式是在 app 的项目中加入相应的扩展的 target。因为扩展一般来说是展现在系统级别的
UI 或者是其他应用中的,Apple 特别指出,扩展应该保持轻巧迅速,并且专注功能单一,在不打扰或者中断用户使用当前应用的前提下完成自己的功能点。因为用户是可以自己选择禁用扩展的,所 以如果你的扩展表现欠佳的话,很可能会遭到用户弃用,甚至导致他们将你的 app 也一并卸载。

扩展的生命周期

扩展的生命周期和包含该扩展的你的容器 app (container app) 本身的生命周期是独立的,准确地说。它们是两个独立的进程,默认情况下互相不应该知道对方的存在。扩展需要对宿主 app (host app,即调用该扩展的 app) 的请求做出相应,当然,通过进行配置和一些手段,我们可以在扩展中访问和共享一些容器 app 的资源,这个我们稍后再说。

因为扩展其实是依赖于调用其的宿主 app 的,因此其生命周期也是由用户在宿主 app 中的行为所决定的。一般来说,用户在宿主 app 中触发了该扩展后,扩展的生命周期就开始了:比如在分享选项中选择了你的扩展,或者向通知中心中添加了你的 widget 等等。而所有的扩展都是由 ViewController 进行定义的,在用户决定使用某个扩展时,其对应的 ViewController 就会被加载,因此你可以像在编写传统 app 的 ViewController 那样获取到诸如 viewDidLoad 这样的方法,并进行界面构建及做相应的逻辑。扩展应该保持功能的单一专注,并且迅速处理任务,在执行完成必要的任务,或者是在后台预约完成任务后,一般需
要尽快通过回调将控制权交回给宿主 app,至此生命周期结束。

按照 Apple 的说法,扩展可以使用的内存是远远低于 app 可以使用的内存的。在内存吃紧的时候,系统更倾向于优先搞掉扩展,而不会是把宿主 app 杀死。因此在开发扩展的时候,也一定需要注意内存占用的限制。另一点是比如像通知中心扩展,你的扩展可能会和其他开发人员的扩展共存,这样如果扩展阻塞了 主线程的话,就会引起整个通知中心失去响应。这种情况下你的扩展和应用也就基本和用户说再见了..

扩展和容器应用的交互

扩展和容器应用本身并不共享一个进程,但是作为扩展,其实是主体应用功能的延伸,肯定不可避免地需要使用到应用本身的逻辑甚至界面。在这种情况 下,我们可以使用 iOS 8 新引入的自制 framework 的方式来组织需要重用的代码,这样在链接 framework 后 app 和扩展就都能使用相同的代码了。

另一个常见需求就是数据共享,即扩展和应用互相希望访问对方的数据。这可以通过开启 App Groups 和进行相应的配置来开启在两个进程间的数据共享。这包括了使用 NSUserDefaults 进行小数据的共享,或者使用 NSFileCoordinator 和 NSFilePresenter 甚至是 CoreData 和 SQLite 来进行更大的文件或者是更复杂的数据交互。

另外,一直以来的自定义的 url scheme 也是从扩展向应用反馈数据和交互的渠道之一。

这些常见的手段和策略在接下来的 demo 中都会用到。一张图片能顶千言万语,而一个 demo 能顶千张图片。那么,我们开始吧。

Timer Demo

Demo 做的应用是一个简单的计时器,即点击开始按钮后开始倒数计时,每秒根据剩余的时间来更新界面上的一个表示时间的 Label,然后在计时到 0 秒时弹出一个 alert,来告诉用户时间到,当然用户也可以使用 Stop 按钮来提前打断计时。其实这个 Demo 就是我的很早之前做的一个番茄工作法的 app 的原型。

为了大家方便跟随这个 demo,我把初始的时候的代码放到 GitHub 的 start-project 这个 tag 上了。语言当然是要用 Swift,界面因为不是 demo 的重点,所以就非常简单能表明意思就好了。但是虽然简单,却也是利用了上一篇文章中所提到的 Size Classes 来完成的不同屏幕的布局,所以至少可以说在思想上是完备的 iOS 8 兼容了 =_=..

初始工程运行起来的界面大概是这样的:



初始工程

简单说整个项目只有一个 ViewController,点击开始按钮时我们通过设定希望的计时时间来创建一个 Timer 实例,然后调用它的 start 方法。这个方法接收两个参数,分别是每次剩余时间更新,以及计时结束(不论是计时时间到的完成还是计时被用户打断)时的回调方法。另外这个方法返回一个 tuple,用来表示是否开始成功以及可能的错误。

剩余时间更新的回调中刷新界面 UI,计时结束的回调里回收了 Timer 实例,并且显示了一个 UIAlertController。用户通过点击 Stop 按钮可以直接调用 stop 方法来打断计时。直接简单,没什么其他的 trick。

我们现在计划为这个 app 做一个 Today 扩展,来在通知中心中显示并更新当前的剩余时间,并且在计时完成后显示一个按钮,点击后可以回到 app 本体,并弹出一个完成的提示。

添加扩展 Target

第一步当然是为我们的 app 添加扩展。正如在总览中所提到的,扩展是项目中的一个单独的 target。在 Xcode 6 中, Apple 为我们准备了对应各类不同扩展点的 target 模板,这使得向 app 中添加扩展非常容易。对于我们现在想做的 Today 扩展,只需点选菜单的 File > New > Target...,然后选择 iOS 中的 Application Extension 的 Today Extension 就行了。



添加 target

在弹出的菜单中将新的 target 命名为 SimpleTimerTodayExtenstion,并且让 Xcode 自动生成新的 Scheme,以方便测试使用。我们的工程中现在会多出一个和新建的 target 同名的文件夹,里面主要包含了一个 .swift 的 ViewController 程序文件,一个叫做 MainInterface 的 storyboard 文件和 Info.plist。其中在 plist 里 的 NSExtension 中定义了这个 扩展的类型和入口,而配套的 ViewController
和 StoryBoard 就是我们的扩展的具体内容和实现了。

我们的主题程序在编译链接后会生成一个后缀为 .app 的包,里面包含主程序的二进制文件和各种资源。而扩展 target 将单独生成一个后缀名为 .appex 的文件包。这个文件包将随着主体程序被安装,并由用户选择激活或者添加(对于 Today widget 的话在通知中心 Today 视图中的编辑删增,对于其他的扩展的话,使用系统的设置进行管理)。我们可以看到,现在项目的 Product 中已经新增了一个扩展了。



扩展的product

如果你有心已经打开了 MainInterface 文件的话,可以注意到 Apple 已经为我们准备了一个默认的 Hello World 的 label 了。我们这时候只要运行主程序,扩展就会一并安装了。将 Scheme 设为 Simple Timer 的主程序,Cmd + R,然后点击 Home 键将 app 切到后台,拉下通知中心。这时候你应该能在 Toady 视图中找到叫做 SimpleTimerTodayExtenstion 的项目,显示了一个 Hello World 的标签。如果没有的话,可以点击下面的编辑按钮看看是不是没有启用,如果在编辑菜单中也没有的话,恭喜你遇到了和
Session 视频里的演讲者同样的 bug,你可能需要删除应用,清理工程,然后再安装试试看。一般来说卸载再安装可以解决现在的 beta 版大部分的无法加载的问题,如果还是遇到问题的话,你还可以尝试重启设备(按照以往几年的 SDK 的情况来看,beta 版里这很正常,正式版中应该就没什么问题了)。

如果一切正常的话,你能看到的通知中心应该类似这样:



Hello World widget

这种方式运行的扩展我们无法对其进行调试,因为我们的调试器并没有 attach 到这个扩展的 target 上。有两种方法让我们调试扩展,一种是将 Scheme 设为之前 Xcode 为我们生成的 SimpleTimerTodayExtenstion,然后运行时选择从 Today 视图进行运行,如图;另一种是在扩展运行时使用菜单中的 Debug > Attach to Process > By Process Identifier (PID) or name,然后输入你的扩展的名字(在我们的 demo 中是 com.onevcat.SimpleTimer.SimpleTimerTodayExtension)来把调试器挂载到进程上去。



调试扩展
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: