应用扩展(App Extension)虽然是 iOS8.0 推出的特性,但是在之后的版本中应用的比较广泛,比如:iOS10 中 Notification Service、Notification Content 和 Intents 等等。这里的应用扩展和类扩展可以类比两个概念,所以应用扩展其实是系统提供了一些扩展接入点来给开发者一定的自定义空间来为系统添加附加的功能。Apple 特别指出,扩展应该保持轻巧迅速,并且专注功能单一,在不打扰或者中断用户使用当前应用的前提下完成自己的功能点。

几个关键字

  • extension point
    系统中支持 exstension 的区域,到目前为止,对于 iOS 应用扩展接入点有:
    • 分享扩展,分享网站内容或者其他对象
    • 动作扩展,点击 Action 按钮后通过判断上下文来将内容发送到应用
    • Today 扩展,在通知中心提供一个快速更新或者简单的任务界面
    • 照片编辑扩展,可以编辑照片括或者视频
    • 文档提供扩展,提供文档的管理
    • 自定义键盘,提供用户可选的自定义键盘或者输入法
    • Intents 扩展,我们的应用和 Siri 对象进行交互
    • Intents UI 扩展,提供 Siri 对象响应的交互界面
    • Messages 扩展,
    • Notification Content 扩展,提供自定义推送页面功能
    • Notification Service 扩展,完成推送额外的操作,图片下载之类
    • Sticker Pack 扩展,Message 中的扩展,自定义表情包

以上是 iOS 系统中给开发者提供的扩展接入点,虽然这些扩展所涉及到的功能比较有限,但是在 iOS8 之前,我们不能对系统中的模块加添某些自定义的功能,相比之下,以上接入点的可操作空间,算是比较大了。

  • app extension
    extension 并不是一个独立的 app,它和一般的 app 不同,但是它也有独立的 bundle,经过编译链接之后的 bundle 形成一个.appex后缀的文件

  • containing app
    包含 extension 的 app。在 iOS 中 extension 不能独立存在,他会随着该 app 的安装,同样 app 卸载之后它的 extension 也就卸载了

  • host app
    能够调起 extension 的 app。像 Share 扩展的 host app 是 Safari

扩展的生命周期

扩展的生命周期和包含该扩展的 app 的生命周期是相互独立的。所以扩展的生命周期依赖于 containing app。只有当用户与 host app 交互并触发这个扩展,此时是该扩展的生命周期的开始,扩展完成执行的任务,并将数据回调给 containing app,此时扩展的生命周期也就结束了。

extension 和 app 通讯问题

  1. extension 可以直接和 host app 进行通讯,并且它们之间是通过 IPC 通讯,这部分我们不需要关心,因为这部分 Apple 已经经过高层封装。
  2. extesion 可以直接和 containing app 通讯,通过extensionContext 对象,这部分也是经过封装的 IPC 通讯。我们可以用extensionContext对象来 open 一个页面。
1
2
3
4
5
extension UIViewController : NSExtensionRequestHandling {

@available(iOS 8.0, *)
open var extensionContext: NSExtensionContext? { get }
}
  1. host app 和 containing app 之间不通讯。
  2. 数据共享。上面讲过 containing app 和 extension 的 bundle 是相互独立的,它们之间的数据、资源时不可能直接相互读取的,但是 Apple 在 iOS8 之后给我提供了一个 App Groups 这样的机制,将一些 bundle 划分再同一个 group 中,此时这些 bundle 可以通过 UserDefault 等方式来完成一些简单的数据传输。

Today 扩展的实践

Today 扩展就是 widget 中添加一个较小视图来提供用户快速阅读一些信息。并且不需要解锁,大大提高获取信息效率。下面的图片 iOS10 中 3D Touch 的效果,可以选择添加 widget ,之后。就可以在 widget 中阅读一些信息或者对某件事情的倒计时来提醒用户。

添加 Target

在项目的 targets 添加一个 Today Extension。系统会为我们生成一些文件,通过文件目录,我们也可以发现,其中 TodayViewController 启示是一个视图控制器,那么我们之后的操作其实和我们熟悉的没什么不同了。

数据共享

在 Capabilities 中打开 App Groups 添加一个 container name 比如 group.todayExtensioinDemo ,需要注意的是共享数据的 bundle 他们的 container name 必须一致。
App Groups 数据共享的方式:

  • NSUserDefaults、URL scheme 小数据量共享,简单数据
  • File、CoreData 和 SQLite 大数据量,复杂数据

在 containing app 中设置

1
2
let userDefault = UserDefaults(suiteName: "group.extension.demo")
userDefault?.set("today test!", forKey: "com.acumen.test")

在扩展中读取

1
2
let userDefault = UserDefaults(suiteName: "group.extension.demo")
let string = userDefault?.value(forKey: "com.acumen.test")

扩展启动 containing app

启动一个 app 我们可以使用 URL scheme 来完成,问题是我们一般使用 URL scheme 启动都是通过 appdelegate 调用 openURL 这个方法,但是在扩展中我们拿不到 appdelegate 这个实例。Apple 给我们提供了 NSExtensionContext 类来得到扩展的上下文,其中 open 方法来打开一个 url。这样我们的问题就解决了。接下来就是只要在 containing app 添加一个 URL type ,任务就完成了。

URL scheme 作为 URL 的 scheme,这里还可以加一些参数传到 containing app。

1
extensionContext?.open(URL(string: "todayextension://test")!, completionHandler: nil)

总结

Apple 确实把一些系统级别的东西开发给我开发者了,通过扩展的方式,虽然提供的可自定义不多,相对苹果爸爸这样的高姿态,已经算比较多了。

  • extension point、host app 等关键字
  • 注意扩展和 containing app 的 container name 必须一致
  • 扩展的启动与否是由 containing app 的运行情况决定的

参考

iOS 通知中心扩展制作入门
官方文档
App Extensions学习笔记

写在最后

github 上的 demo :ExtensionDemo
希望对你有帮助,写的不正确的地方,欢迎拍砖~