UIApplicationDelegate launchOptions
AppDelegate 是 iOS 各种功能的集散中心。
应用生命周期管理?URL 路由?通知?Core Data 咒语?各种三方 SDK 的初始化?一些似乎放在哪里都不合适的零散功能?统统丢进 App
里吧!
在 AppDelegate 所有这些拥挤的、超出负载的方法中,-application:didFinishLaunchingWithOptions: 是最臃肿的一个了。
对于很多开发者来说 launch
参数就是类似于 Java 的 main
函数中 String[] args
的作用 —— 在构建应用时候一般是被忽略的。在其平淡的外表下,launch
其实隐藏着 iOS 应用启动时携带的大量核心信息。
NSHipster 本周披露的知识点是关于我们平时关心最少的、但又是 UIKit 中最重要的东西:launch
。
每个应用都使用 UIApplication
(更精确地说以后或许也包含 -application:will
) 启动。应用调用这个方法来告诉 delegate 进程已经启动完毕,已经准备好运行了。
在 Springboard 中点击图标应用就开始启动了,但也有其他一些启动的方法。比如说注册了自定义 URL scheme 的应用可以以类似于 twitter://
的方式从一个 URL 启动。应用可以通过推送通知或地理位置变更启动。
查明应用为什么以及是如何启动的,就是 launch
参数的职责所在。就像 user
字典一样,在 -application:did
的 launch
中也包含很多特别命名的键。
这些键中的许多在应用启动时发出的
UIApplication
的通知中也可用,详细内容请查阅文档。Did Finish Launching Notification
launch
包含的键太多了,按照应用启动原因分组理解起来更容易一点:
从 URL 打开
其他应用通过传递 URL 可以打开一个应用:
[[UIApplication shared Application] open URL:[NSURL URLWith String:@"app://..."]];
例如:http://
开头的 URL 会在 Safari 中打开,mailto://
开头的 URL 会在邮件中打开,tel://
开头的 URL 会在电话中打开。
这些情况下 UIApplication
键就会很常用了。
UIApplication
: 标示应用是通过 URL 打开的。其对应的值代表应用被打开时使用的Launch Options URLKey NSURL
对象。
应用也可以通过 URL 和附加系统信息打开。当应用从 AirDrop 的 UIDocument
中打开时,launch
会包含下列这键:
UIApplication
:请求打开应用的应用 id。对应的值是请求打开应用的 bundle ID 的Launch Options Source Application Key NSString
对象UIApplication
:标示通过 URL 打开应用时携带了自定义数据。对应的值是包含自定义数据的属性列表对象Launch Options Annotation Key
NSURL *file URL = [[NSBundle main Bundle] URLFor Resource:@"Document" with Extension:@"pdf"];
if (file URL) {
UIDocument Interaction Controller *document Interaction Controller = [UIDocument Interaction Controller interaction Controller With URL:file URL];
document Interaction Controller.annotation = @{@"foo": @"bar"};
[document Interaction Controller set Delegate:self];
[document Interaction Controller present Preview Animated:YES];
}
响应通知
不要和 NSNotification
混淆了,应用可以通过本地(local)或远程(remote)通知打开。
推送通知
自 iOS 3 开始引入的 remote(或者叫 push)notification 是在移动平台上的重要特性。
在 application:did
中调用 register
来注册推送通知。
[application register For Remote Notification Types:
UIRemote Notification Type Badge |
UIRemote Notification Type Sound |
UIRemote Notification Type Alert];
如果调用成功则会回调 -application:did
,之后该设备就能随时收到推送通知了。
如果应用在打开时收到了推送通知,delegate 会调用 application:did
。但是如果是通过在通知中心中滑动通知打开的应用,则会调用 application:did
并携带 UIApplication
启动参数:
UIApplication
:标示推送通知目前处于可用状态。对应的值是包含通知内容的Launch Options Remote Notification Key NSDictionary
。
alert
:一个字符串或包含两个键body
和show-view
的字典。badge
:标示从通知发出者那应该获取数据的数量。这个数字会显示在应用图标上。没有 badge 信息则表示应该从图片上移除数字显示。sound
:通知接收时播放音频的文件名。如果值为 “default” 那么则播放默认音频。
因为通知可以通过两种方式控制,通常的做法是在 application:did
中手动调用 application:did
:
- (BOOL)application:(UIApplication *)application
did Finish Launching With Options:(NSDictionary *)launch Options
{
// ...
if (launch Options[UIApplication Launch Options Remote Notification Key]) {
[self application:application did Receive Remote Notification:launch Options[UIApplication Launch Options Remote Notification Key]];
}
}
本地通知
本地通知 是在 iOS 4 中加入的功能,这个功能至今都被误解了。
应用可以制定计划在未来某个时间点触发 UILocal
。如果应用处于打开状态,那么将回调 -application:did
方法。如果应用处于非活跃状态,通知将会被发送到通知中心。
不像推送通知,UIApplication
的 delegate 提供了统一控制本地通知的方法。如果应用是通过本地通知启动的,-application:did
将会在 -application:did
之后被自动调用(意思就是不需要像推送通知一样在 -application:did
中手动调用了)。
本地通知会在启动参数中携带和推送通知有类似结构的 UIApplication
:
-
UIApplication
: 标示本地通知目前处于可用状态。对应的值是包含通知内容的Launch Options Local Notification Key NSDictionary
。
如果应用在运行中收到本地通知需要显示提示框、其他情况不显示提示框,可以手动从 UILocal
获取相关信息进行操作:
// .h
@import AVFoundation;
@interface App Delegate ()
@property (readwrite, nonatomic, assign) System Sound ID local Notification Sound;
@end
// .m
- (void)application:(UIApplication *)application
did Receive Local Notification:(UILocal Notification *)notification
{
if (application.application State == UIApplication State Active) {
UIAlert View *alert View =
[[UIAlert View alloc] init With Title:notification.alert Action
message:notification.alert Body
delegate:nil
cancel Button Title:NSLocalized String(@"OK", nil)
other Button Titles:nil];
if (!self.local Notification Sound) {
NSURL *sound URL = [[NSBundle main Bundle] URLFor Resource:@"Sosumi"
with Extension:@"wav"];
Audio Services Create System Sound ID((__bridge CFURLRef)sound URL, &_local Notification Sound);
}
Audio Services Play System Sound(self.local Notification Sound);
[alert View show];
}
}
- (void)application Will Terminate:(UIApplication *)application {
if (self.local Notification Sound) {
Audio Services Dispose System Sound ID(self.local Notification Sound);
}
}
地理位置事件
听说你在开发一个 LBS 签到照片的应用?哈哈,看起来你的动作好像落后时代四年多了。
但不要害怕!有了 iOS 的位置监控,你的应用可以通过地理位置触发的事件启动了:
UIApplication
:标示应用是响应地理位置事件启动的。对应的值是包含 Boolean 值的Launch Options Location Key NSNumber
对象。可以把这个键作为信号来创建CLLocation
对象并开始进行定位。位置数据会传给 location manager 的 delegate(并不需要这个键)。Manager
以下是检测位置变化来判断启动行为的例子:
// .h
@import Core Location;
@interface App Delegate () <CLLocation Manager Delegate>
@property (readwrite, nonatomic, strong) CLLocation Manager *location Manager;
@end
// .m
- (BOOL)application:(UIApplication *)application
did Finish Launching With Options:(NSDictionary *)launch Options
{
// ...
if (![CLLocation Manager location Services Enabled]) {
[[[UIAlert View alloc] init With Title:NSLocalized String(@"Location Services Disabled", nil)
message:NSLocalized String(@"You currently have all location services for this device disabled. If you proceed, you will be asked to confirm whether location services should be reenabled.", nil)
delegate:nil
cancel Button Title:NSLocalized String(@"OK", nil)
other Button Titles:nil] show];
} else {
self.location Manager = [[CLLocation Manager alloc] init];
self.location Manager.delegate = self;
[self.location Manager start Monitoring Significant Location Changes];
}
if (launch Options[UIApplication Launch Options Location Key]) {
[self.location Manager start Updating Location];
}
}
报刊杂志(Newsstand)
所有的报刊杂志开发者都会为此欢呼的!
欢呼声.aiff
够了…
有新的可用下载时,报刊杂志应用可以启动。
这样注册即可:
[application register For Remote Notification Types:
UIRemote Notification Type Newsstand Content Availability];
然后在启动参数中找到这个键:
UIApplication
:标示应用有新的可用杂志资源下载。对应的值是包含Launch Options Newsstand Downloads Key NKAsset
id 的字符串数组。虽然你可以通过这些 id 进行检查,但还是应该通过Download NKLibrary
对象的 downloadingAssets 属性来持有这些NKAsset
对象(可用用于展示下载进度或错误)以便显示在报刊杂志书架中。Download
详细情况不再赘述。
蓝牙
iOS 7 开始支持外围蓝牙设备重新唤醒应用。
应用启动后通过特定的 id 实例化一个 CBCentral
或 CBPeripheral
用于连接蓝牙设备,之后应用就可以通过蓝牙系统的相关动作来被重新唤醒了。取决于发出通知的是一个中心设备还是外围设备,launch
会传入以下两个键中的一个:
UIApplication
:标示应用之前曾有过一个或多个Launch Options Bluetooth Centrals Key CBCentral
对象并被蓝牙系统的相关动作唤醒过。对应的值是包含Manager NSString
对象的数组。数组中每一个字符串表示一个中心设备的恢复连接 id。UIApplication
:标示应用之前曾有过一个或多个Launch Options Bluetooth Peripherals Key CBPeripheral
对象并被蓝牙系统的相关动作唤醒过。对应的值是包含Manager NSString
对象的数组。数组中每一个字符串表示一个外围设备的恢复连接 id。
// .h
@import Core Bluetooth;
@interface App Delegate () <CBCentral Manager Delegate>
@property (readwrite, nonatomic, strong) CBCentral Manager *central Manager;
@end
// .m
self.central Manager = [[CBCentral Manager alloc] init With Delegate:self queue:nil options:@{CBCentral Manager Option Restore Identifier Key:(launch Options[UIApplication Launch Options Bluetooth Centrals Key] ?: [[NSUUID UUID] UUIDString])}];
if (self.central Manager.state == CBCentral Manager State Powered On) {
static NSString * const UID = @"7C13BAA0-A5D4-4624-9397-15BF67161B1C"; // generated with `$ uuidgen`
NSArray *services = @[[CBUUID UUIDWith String:UID]];
NSDictionary *scan Options = @{CBCentral Manager Scan Option Allow Duplicates Key:@YES};
[self.central Manager scan For Peripherals With Services:services options:scan Options];
}
要搞清楚所有的启动参数确实要费点力气。幸运的是一般的应用只需要处理其中的一两种就够了。
了解所有的可能性需要从理解概念走到亲自实现的步骤上。当你了解到事情有这么多可能性的时候,下一个改变世界的想法可能就已经在脑中开始酝酿了。