SiriKit实践 - AppExtension介绍

之前把SiriKit的文档啃完了,然后找了很久也没找到完成度比较高的Demo。之前手头上比较忙就先搁置了,最近有时间可以继续了。索性自己摸石头过河,一步步试着实现。
要说SiriKit当然要先了解App Extension了,本篇先介绍一下App Extension。

App Extension

App Extension顾名思义其是对App的扩展,自iOS8开始新增的特性。之前常见有Today(Widget)Custom KeyboardShare,iOS9和10之后陆续又增加了不少,如最近比较热的iMessageStickerContent BlockerCall Directory 以及本次要分享的SiriKit

20-SiriKit-Practice-01

特点与区别

App Extension

App Extension在工程中以一个target存在,有自己的info.plist,有自己的配置项,也有自己的bundle id(如:主App的为com.company.xAppName,那么AppExtension通常为com.company.xAppName.xExtensionName)。它的bundle也是独立的,bundle后缀为.appex

Containing App

我们的主App就是但是Containing App了,实际上App Extension并不是一个独立的App,它不能独立安装与卸载,他是随着主App的安装而安装、卸载而卸载的。而且提交AppStore的时候主App必须要实现一些功能。

Host App

调用Extions的App就是Host App,比如Intent ExtensionHost AppSiri

App Extension是与Host App进行通讯,App ExtensionContaining App通过openUrl:进行通讯,而Host AppContaining App之间就没有任何关系了,大致关系如下图:

生命周期

它的生命周期与主App也不同,App Extension是随着用户触发了而创建和启动,用户取消操作或者离开后一段时间他会被系统自动杀死。
AppExtension生命周期

数据与代码共享

毕竟App ExtensionContaining App并不是一体的,所以数据和代码上的共享是有些需要注意的。

数据共享

开启App Group

首先你需要给需要数据共享的target开启App Group并设置Group Id(只有开发者账号可以进行),注意共享数据的target的group id要勾选一样的。

如下图,我的主App target和Intent Extension的Target开启并设置了GroupId(没有group id需要先点+号增加一个)

20-SiriKit-Practice-02

20-SiriKit-Practice-03

NSUserDefaults方式共享

使用NSUserDefaults的初始化方法initWithSuiteName:来获取共享的NSUserDefaults对象,再进行操作

1
2
3
4
5
NSString *groupId = @"你取的App Group的Id";
NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName: groupId];
//然后正常的取值和赋值就行了
[userDefault setValue:@"value" forKey:@"key" ];
[userDefault valueForKey:@"key"];

NSFileManager方式共享

使用NSFileManager对象的containerURLForSecurityApplicationGroupIdentifier:方法,然后获取共享目录路径,再进行操作,如:

1
2
3
4
5
NSString *groupId = @"你取的App Group的Id";
NSString *groupPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: groupId].path;
NSLog(@"groupPath: %@", groupPath);
//然后用writeToFile、某个类的WithContentsOfFile之类的操作进行读写就行了

使用数据库

使用SQLite、CoreData等数据库进行共享,但是数据库文件还是得靠NSFileManager来读取

主App与Extension各种路径

在主App和Extension中分别打印groupPath、bundlePath和sandyPath,可以发现,它们的groupPath是一样的,bunldePath和sandyPath都不同

1
2
3
4
5
6
7
8
9
10
11
12
13
//---主App打印
groupPath: /private/var/mobile/Containers/Shared/AppGroup/4616FF64-1879-4A12-BCC4-C2E7F82C612A
bundlePath: /var/containers/Bundle/Application/ED47E5E2-F867-4179-AF99-0609F98CA4DA/SiriKitTest.app
sandyPath: /var/mobile/Containers/Data/Application/A79D26BD-B003-4BDB-B3C8-3B73FA7AB08B
//---Extension打印
groupPath: /private/var/mobile/Containers/Shared/AppGroup/4616FF64-1879-4A12-BCC4-C2E7F82C612A
bundlePath: /private/var/containers/Bundle/Application/03D71C80-B9C0-4480-82A7-044D129614F3/SiriKitTest.app/PlugIns/SendMessageIntentUI.appex
sandyPath: /var/mobile/Containers/Data/PluginKitPlugin/E241732D-5383-416C-A411-222B8C780E2B

代码和图片资源共享

使用Framework

将两边都会用到的代码,打成Framework,然后引入

使用CocoaPods

制作私有CocoaPods库,进行引入。Podfile注意给Intent或者IntentUI的target也加上需要的。如:
20-SiriKit-Practices-04-1

勾选target membership

对已有的一些要共用的类在右侧勾选,新建类的时候也考虑是否要勾选。

20-SiriKit-Practice-05
20-SiriKit-Practice-06

坑点

  1. 报错UserDefaults无法读写

    1
    2
    3
    4
    handle status: 02016-12-15 15:04:17.414706 SendMessageIntentUI[13492:1016495] [User Defaults] Failed to read values in
    CFPrefsPlistSource<0x1700e9a00> (Domain: group.com.maihaoche.SiriKitTest,
    User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null)):
    Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

    出现这个错误一般处理:

    • group id配置是否错误
    • 初始化NSUserDefaults方法错误
      使用[[NSUserDefaults alloc] initWithSuiteName:@"xxxx"]
      而非[[NSUserDefaults standardUserDefaults] initWithSuiteName:@"xxxx"]
    • 以上两种均无错的时候,卸载App,clean工程,重新run。但不一定有效。😂
  2. 有些属性/方法会标注NS_EXTENSION_UNAVAILABLE以表明App Extension无法使用

参考