iOS - App Extension 整体总结
一、App Extension的介绍
App Extension可以让你扩展你APP的自定义功能和内容,使用户可以在与其他应用或者系统进行互动的时候去使用它。app extension即为本文所说的extension。extension并不是一个独立的app,它有一个包含在app bundle中的独立bundle,extension的bundle后缀名是.appex。其生命周期也和普通app不同,这些后文将会详述。extension不能单独存在,必须有一个包含它的containing app。扩展(app Extension )是 iOS 8 中引入的一个非常重要的新特性
我们平时看到的Widget、微信和QQ的share等等,都是App Extension,下图是一些例子:
二、 Extension的种类
我们可以在Xcode的File--->New--->Target里面看到不同平台的Extension,包括iOS、watchOS、tvOS、macOS等等。这里主要介绍iOS,主要包括以下几种Extensions:
也可直接参考官方文档
iOS 8 系统有 6 个支持扩展的系统区域,分别是 Today 、 Share 、 Action 、 Photo Editing 、 Storage Provider 、 Custom keyboard 。支持扩展的系统区域也被称为扩展点。
Today Widget
Today扩展 可以快速获取更新或者在通知中心的近日视图中执行一项快速任务。对于赛事比分,股票、天气、快递这类需要实时获取的信息,可以在通知中心的Today 视图中创建一个 Today 扩展实现。 Today 扩展又称为 Widget 。
Share Extension
分享扩展,发布一个共享网站或者与其他应用共享内容,在 iOS 8 之前,用户只有 Facebook,Twitter 等有限的几个分享选项可以选择。在 iOS 8 中,开发者可以创建自定义的分享选项。
Action Extension
动作扩展,在另一个应用程序的上下文中操作或者查看内容, 在所有支持的扩展点中扩展性最强的一个。它可以实现转换另一个 app 上下文中的内容。苹果在 WWDC 大会上演示了一个 Bing 翻译动作扩展,它可以将在 Safari 中选中的文本翻译成不同的语言。
Photo Editing
图片编辑扩展,在照片app中编辑照片或者视频,在 iOS 8 之前,如果你想为你的照片添加一个特殊的滤镜,你需要进入第三方 app 中,这个过程是相当繁琐的。在 iOS 8 中,你可以直接在 Photos 中使用第三方 app ,如 Instagram , VSCO cam 、 Aviary 提供的 Photo Editing 扩展完成对图片的编辑,而无需离开当前的 app 。
Document Provider
Document Provider 让跨多个文件存储服务之间的管理变得更简单。类似 Dropbox 、 Google Drive 等存储提供商通过在 iOS 8 中提供一个 Document Provider 扩展, app 直接可以使用这些扩展检索和存储文件而不再需要创建不必要的拷贝。
Custom Keyboard
键盘扩展,例如第三方的键盘,搜狗输入法,百度输入法等。苹果公司在 2007 年率先推出了触摸屏键盘,但一直没多大改进。在这一方面, Android 则将键盘权限开放给了第三方开发者,所以出现了许多像 Swype , SwiftKey 等优秀的键盘输入法。在 iOS 8 中,苹果终于将键盘权限开发给了第三方开发者,自定义键盘输入法可以让用户在整个系统范围内使用。
以下是iOS 9和之后中新增扩展
1.Audio Unit Extension:音频单元扩展
2.Broadcast UI Extension:广播UI 扩展
3.Broadcast Upload Extension:广播上传扩展
4.Call Directory Extension:呼叫目录扩展
5.Content Blocker Extension:内容拦截器扩展
6.iMessage Extension:消息的扩展
7.Intents Extension:Intents扩展
8.Intents UI Extension:Intents UI扩展
9.Notification Content Extension:通知内容扩展
10.Notification Service Extension:通知服务扩展
11.Shared Links Extension:分享链接扩展
12.Spotlight Index Extension:Spotlight 索引扩展
13.Sticker Pack Extension:贴纸包扩展
三、App Extensions的生命周期
以下是苹果官方提供的图片:
1.用户选择要使用的App extension
2.系统启动App Extension
3.App Extension 代码运行
4.运行完之后系统kill掉App Extension
这就是App Extension的生命周期,举个例子:
一个Share Extension,在图库里面你选择了一张图片,然后点击分享,选择你的Share Extension(第一步),此时系统会启动你的Share Extension(第二步)。然后你将选择的图片分享到指定的程序(例如微信的发送给朋友)(第三步)。接下来分享页面关闭,系统kill掉了Share Extension。
四、App Extension的通信方式
App Extension主要的通信是和他的host app
Host app (如微信) ; App extension (safari里面分享点击出来的微信extension);Containing app (safari)
这个展示的就是正在运行的App Extension、host app和containing app之间的关系。可以看出:Containing App和app Extension并没有直接的沟通。甚至有的时候Containing app可以不运行,而App Extension直接运行。Containing app和Host app没有任何的沟通。
在一个典型的request/response中,系统打开代表host app(图库)的extension(微信分享的share extension),把host app提供的数据(图片和选择的好友)输送到extension的context,然后extension展示界面,提供一些功能任务(例如微信的分享到朋友)。
还有一种是app extension可以直接和他的containing app沟通:
例如Today Widget,可以直接告诉系统打开他的Containing app,只需要调用NSExtensionContext的openURL:CompletionHandler:方法即可。
app extension和containing app可以共同读写一个被称为Shared resources的存储区域,这是通过App Groups实现的。
这里需要注意的是:
但是如果你不怕苹果审核不通过 或者 下架 也可以用以下方法打开:
// UIWebView *webView = [[UIWebView alloc]init]; // webView.hidden = YES; // NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:@"weixin://"]]; // [webView loadRequest:request]; // [self.view addSubview:webView]; //这个方法已经打不开了 UIResponder* responder = self; while ((responder = [responder nextResponder]) != nil) { if ([responder respondsToSelector:@selector(openURL:)] == YES) { [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:[NSString stringWithFormat:@"weixin://"]]]; } }
stackoverflow 里面有关于 Share Extension to open containing app 相关的解答
五、App Groups 实现数据共享
- TARGETS-->AppExtensionDemo-->Capabilities-->App Groups
- TARGETS-->TodayExtension-->Capabilities-->App Groups
三、extension和containing app数据共享
- (void)saveTextByNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; [shared setObject:_textField.text forKey:@"wangzz"]; [shared synchronize]; }
- (NSString *)readDataFromNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; NSString *value = [shared valueForKey:@"wangzz"]; return value; }
- (BOOL)saveTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = _textField.text; BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; if (!result) { NSLog(@"%@",err); } else { NSLog(@"save value:%@ success.",value); } return result; }
- (NSString *)readTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; return value; }
- (BOOL)copyFrameworkFromMainBundleToAppGroup { NSFileManager *manager = [NSFileManager defaultManager]; NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; BOOL removeResult = [manager removeItemAtPath:desPath error:&err]; if (!removeResult) { NSLog(@"%@",err); } else { NSLog(@"remove success."); } BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err]; if (!copyResult) { NSLog(@"%@",err); } else { NSLog(@"copy success."); } return copyResult; }
- (BOOL)loadFrameworkInAppGroup { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; NSBundle *bundle = [NSBundle bundleWithPath:desPath]; BOOL result = [bundle loadAndReturnError:&err]; if (result) { Class root = NSClassFromString(@"Person"); if (root) { Person *person = [[root alloc] init]; if (person) { [person run]; } } } else { NSLog(@"%@",err); } return result; }
六、在App Extension中不可以做的事情
一个app extension不能有以下情况:
1.访问sharedApplication对象。因此不能使用任何该对象的防范
2.使用任何标记NS_EXTENSION_UNAVAILABLE宏的API,或者类似的宏,或者不可用framework里面的API,例如HealthKit framework不能用于app extensions
3.iOS设备访问相机或者麦克风(iMessage app可以访问这些资源,只要在Info.plist里面进行配置使用描述即可)
4.运行一个长时间的后台任务(根据不同平台而异)
5.使用AirDrop接收数据