一、为什么会有这篇文章?
推送通知苹果几乎每年都改,但都是缝缝补补的功效,直到iOS10的出现,可能苹果觉得之前的代码架构已经无法满足他们的功能需求了,于是也就出现了代码重构。多版本的修改多少有点让人混淆,而此文仅在对iOS7-10通知有一定了解的基础上进行流程总结。Demo地址。
二、iOS推送通知各版本到底改了些啥?
在回答之前,我先上一段代码:看看iOS10出来之后,兼容到iOS7最一般的推送通知代码该如何写。
#define IS_IOS_10 [[UIDevice currentDevice].systemVersion floatValue] >= 10.0
#define IS_IOS_9 [[UIDevice currentDevice].systemVersion floatValue] >= 9.0
#define IS_IOS_8 [[UIDevice currentDevice].systemVersion floatValue] >= 8.0
- (void)registerNotification{
if (IS_IOS_10) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
}];
//设置代理
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
}
else if (IS_IOS_8){
//ios8注册通知
UIUserNotificationType type = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:type categories:nil];
[[UIApplication sharedApplication]registerUserNotificationSettings:setting];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}else{
//ios7及之前注册通知
UIRemoteNotificationType type = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
[[UIApplication sharedApplication]registerForRemoteNotificationTypes:type];
}
}
随着版本的更替,远程通知和本地通知两个概念是越来越一体了,到iOS10已经无法从方法名直接区分远程通知还是本地通知了,而是通过UNNotificationTrigger
这个类的子类来区分。需求决定功能,再怎么一体,远程和本地毕竟两个不同的需求,只是代码规范不同了而已。回到前面,先看看通知各版本到底都改了些啥?
太老的版本我也就不列了,截至2017年1月初,iOS9以下(不包括iOS9)的系统覆盖率为6%,应用撑死兼容到iOS7,iOS7以下的那些覆盖率,基本可以忽略。
iOS7 后台静默通知。需要开启
Background Modes
中的Remote notifications
。iOS8 通知请求权限修改。即
UIRemoteNotificationType
由UIUserNotificationType
代替,并加入了UIUserNotificationSettings
。iOS9 通知加入可输入操作。
iOS10 通知重构,全新框架
UserNotifications
。
三、远程通知,本地通知,iOS7到iOS10等那么多的代理方法,怎么区分?又什么时候调用和触发?
对于概念多、各自又有分支和联系的时候,是时候来一张图表了。表中列举了各版本中通知使用相关的类或类库。
1、普通本地通知
怎么发送本地通知这个不多说,Demo里面也有,由于真机系统有限,仅在iOS8-9下分应用状态测试了本地通知的流程,iOS7同样适用。主要理清各版本通知的代理方法具体怎么走。
程序杀死状态。
所有版本,本地通知在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
方法中获取。[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]
即为我们需要的通知对象。没有通知时launchOptions
为空。程序后台状态。
iOS10以下,在点击通知会调用- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
方法。
iOS10会调用- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler
方法。程序前台状态下,iOS10以下不会触发通知栏,并且还是调用
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
方法。
iOS10会立马触发- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler;
方法,并且能根据completionHandler
回调配置通知的显示样式。点击通知后同样会触发- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;
方法。
值得注意的是,程序后台状态下,代理方法都是在用户点击通知后才会执行,收到通知点击应用图标启动是不会走代理方法的。
程序杀死状态下启动应用只会触发
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
方法。
2、注册了category的可操作本地通知
先上个通知具体样式图,方便理解。具体代码直接看Demo。
这是iOS8-9上的通知截图,iOS8-9上可操作通知共分为两种,即图中的按钮1和按钮2,点击按钮1会激活应用程序到前台,按钮2后台触发,不会激活应用。点击这两个按钮都会走如下代理方法:
//本地通知category属性使用,iOS8引入,点击按钮触发。
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler{
//业务逻辑代码写在这。
completionHandler();
}
//本地通知category属性使用,iOS9引入,点击按钮或输入文字触发(当实现了此代理方法时,上面的方法失效)。
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler{
NSLog(@"用户输入的信息为:%@",[responseInfo objectForKey:UIUserNotificationActionResponseTypedTextKey]);
//业务逻辑代码写在这。
completionHandler();
}
下面是iOS10的通知截图。
iOS10上可操作通知共分为三种:
UNNotificationActionOptionForeground //点击通知会激活应用到前台,需要解锁。
UNNotificationActionOptionAuthenticationRequired //点击通知需要解锁但不会激活应用到前台。
UNNotificationActionOptionDestructive //显示为红色字体,不需要解锁。
iOS10所有的通知,即远程推送,本地推送,都只会触发下面两个方法。而区分远程还是本地通知,就是前面提到的
UNNotificationTrigger
的子类,当UNNotificationRequest
的trigger
为UNPushNotificationTrigger
时,说明此通知为远程通知,否则为本地通知。下面为代理方法:
//MARK: iOS10通知代理方法,程序在前台时收到通知调用
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
UNNotificationRequest *request = notification.request;
NSDictionary *userInfo = request.content.userInfo;
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"收到了一个远程推送:%@",userInfo);
}
else{
NSLog(@"收到了一个本地推送:%@",userInfo);
}
//此方法回调,设置程序前台是,banner提示框的显示选项,
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
}
//MARK: iOS10通知代理方法,通知前后台点击时会触发。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
NSDictionary *userInfo = response.notification.request.content.userInfo;
NSLog(@"userInfo:%@",userInfo);
completionHandler();
}
远程通知和本地通知之所以能统一规范,就是因为其原理类似,唯一的区别在于远程通知需要获取用户的token,通过苹果的APNS将通知发送给对应用户,而具体通知的内容则由后台配置的那个aps字典决定了。
3、普通远程通知
远程通知测试的软件我用的这个,选择好证书,输入从- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
方法中获取的deviceToken
,等连接完毕就可以推送一般的远程的通知了。下面是iOS7-9远程通知相关的代理方法(iOS10所有通知只走前面提及的两个方法,不再赘述)。
/**
已经收到远程推送消息
@param userInfo 收到的userInfo信息
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
NSLog(@"userInfo:%@",userInfo);
if (application.applicationState == UIApplicationStateActive) {
NSLog(@"程序运行中收到通知");
}else{
NSLog(@"程序不活跃中收到通知");
}
}
/**
后台模式收到的远程推送信息,需要开启后台模式的远程推送,实现了此方法,上面的方法就失效。
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSLog(@"userInfo:%@",userInfo);
if (application.applicationState == UIApplicationStateActive) {
NSLog(@"程序运行中收到通知");
}else{
NSLog(@"程序不活跃中收到通知");
}
completionHandler(UIBackgroundFetchResultNewData);
}
远程推送需要后台配置title等参数,也就是在本地通知中设置的那些什么alertTitle,alertBody
啥子的。基本格式如下:
{
"aps" : {
"alert" : {
"title" : "主标题",
"subtitle" : "子标题",
"body" : "通知内容"
},
"badge" : 2,
"sound" : "alert.wav"
},
"custom" : "something"
}
- 其中aps为固定对象,后台需要传输自己的参数可以自行添加与aps并列的键值对。
-
subtitle
是iOS10才有的,iOS10之前加不加无所谓。 -
sound
为提示音,默认为default
,自定义时别忘了后缀。 -
custom
为自扩展字段,比如iOS10中图片和视频的连接地址就可以放这里。
4、高级远程通知
所谓高级,无非就是iOS10以下的可操作通知、iOS10中通知的增删改,以及UNNotificationServiceExtension
和UNNotificationContentExtension
这两个通知扩展了。由于是远程通知,这里仅附上测试aps格式,方便测试。
{
"aps" : {
"alert" : {
"title" : "这是title",
"subtitle" : "这是subtitle",
"body" : "这是body"
},
"mutable-content" : "1",
"category" : "myNotificationCategory"
}
}
- 此aps可以用来测试上述所有高级远程通知。
-
category
中的值必须与测试可操作通知时的category
值一样。 - 测试
UNNotificationContentExtension
时,必须和plist文件中的UNNotificationExtensionCategory
所对应的值保持一致,编译对应的Scheme
即可。 - 凡是需要对通知内容做修改的都要加上
mutable-content
字段,UNNotificationServiceExtension
中需要使用。例如下载通知中传来的图片或视频,先下载后再通知。 - 通知的增删改都是根据
request
中的identifier
进行查找和区分的。
通知其实也就这些,只是改的多,显得乱而已。理解了通知的流程与原理,处理这一模块的问题也就能得心应手。