iOS消息推送
iOS消息推送分为本地通知和远程推送(APNs)两种。本地通知即常见的闹钟等在App程序内实现的在一定触发条件下执行的通知方式。远程推送即需要通过苹果的APNs服务器向用户设备发送消息。
iOS10苹果推出新的处理推送的框架UserNotifications,这个框架专门用来处理推送通知。
以远程推送为例,在iOS10之前我们接收远程推送是在AppDelegate的方法里面处理,iOS10使用了UserNotifications,在UNUserNotificationCenterDelegate中接收和处理远程推送。
以本地通知为例,iOS10之前我们使用的是UILocalNotification(属于UIKit)这个类处理本地通知,iOS10使用了UserNotifications中新的类UNNotificationContent等实现本地通知。
使用通知,需要请求用户授权
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//通知对象
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
//设置代理
notificationCenter.delegate = self;
//获取用户授权
[notificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay completionHandler:^(BOOL granted, NSError * _Nullable error) {
//回主线程
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted) {
//用户拒绝授权Coding...
} else {
//用户已授权,注册远程推送
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
});
}];
return YES;
}
这里registerForRemoteNotifications是注册远程通知,如果只是使用本地通知则不需要注册。
一、本地通知
添加一个按钮,点击后添加一个本地通知
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *sender = [UIButton buttonWithType:UIButtonTypeContactAdd];
sender.frame = CGRectMake(CGRectGetWidth(self.view.frame)*0.5-50, 100, 64, 32);
[sender addTarget:self action:@selector(showLocalNotification:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:sender];
}
1、引入框架头文件
#import "ViewController.h"
#import <UserNotifications/UserNotifications.h>
2、创建本地通知
- (void)showLocalNotification:(UIButton *)sender {
//创建本地通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Calendar Title";
content.subtitle = @"This is subtitle";
content.body = @"This is body";
content.userInfo = @{};//这里可以自定义用户数据
content.sound = [UNNotificationSound defaultSound];
content.badge = @([UIApplication sharedApplication].applicationIconBadgeNumber+1);
//设置本地通的知触发机制
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
//创建本地通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"time noti" content:content trigger:trigger];
//创建本地通知对象,设置接收消息代理,
UNUserNotificationCenter *localNotificationCenter = [UNUserNotificationCenter currentNotificationCenter];
localNotificationCenter.delegate = self;
//添加本地通知
[localNotificationCenter addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
}
}];
}
10秒后不管应用程序在后台还是在前台还是杀死了应用程序,都会收到aler通知。
2、接收本地通知
接收本地通知主要是通过UNUserNotificationCenterDelegate
的方法。
UNUserNotificationCenterDelegate提供了两个代理
这个代理方法当应用程序在前台时,收到本地通知会执行。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSLog(@"willPresentNotification");
NSLog(@"title : %@",notification.request.content.title);
NSLog(@"subtitle : %@",notification.request.content.subtitle);
NSLog(@"body : %@",notification.request.content.body);
NSLog(@"userInfo : %@",notification.request.content.userInfo);
//这个回调可以设置当收到通知后, 有哪些效果呈现(声音/提醒/数字角标)
completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
这个代理方法是当用户点击了alert时会执行
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
}
有时候点击alert的时候我们需要判断程序是否在前台,并做不同的处理:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
NSLog(@"applicationState %ld",(long)[UIApplication sharedApplication].applicationState);
switch ([UIApplication sharedApplication].applicationState) {
case UIApplicationStateActive:
//程序在前台时点击了alert会执行这里
break;
case UIApplicationStateInactive:
//应用程序运行在前台但不接收事件,程序从后台刚进入前台时的状态。当在后台点击alert进入前台时,会执行这里。
break;
case UIApplicationStateBackground:
//应用程序在后台,这种状态在这里捕获不到
break;
}
completionHandler();
}
2.1 应用程序在前台
收到本地通知会执行第一个代理方法,点击alert会执行第二个代理方法。处理代码时我们需要进行判断,避免多次处理。
2.2 应用程序在后台
应用程序在后台时,只能通过点击alert后,在第二个代理方法中处理代码,第一个代理方法是不会被执行的。
2.3 应用程序被杀死
应用程序被杀死,也是通过点击alert进入应用程序,这时候在通过第二个代理方法,捕获通知,进行处理。
完整代码
#import "ViewController.h"
#import <UserNotifications/UserNotifications.h>
@interface ViewController ()<UNUserNotificationCenterDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *sender = [UIButton buttonWithType:UIButtonTypeContactAdd];
sender.frame = CGRectMake(CGRectGetWidth(self.view.frame)*0.5-50, 100, 64, 32);
[sender addTarget:self action:@selector(showLocalNotification:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:sender];
}
- (void)showLocalNotification:(UIButton *)sender {
//创建本地通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Calendar Title";
content.subtitle = @"This is subtitle";
content.body = @"This is body";
content.userInfo = @{};//这里可以自定义用户数据
content.sound = [UNNotificationSound defaultSound];
content.badge = @([UIApplication sharedApplication].applicationIconBadgeNumber+1);
//设置本地通的知触发机制
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
//创建本地通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"time noti" content:content trigger:trigger];
//创建本地通知对象,设置接收消息代理,
UNUserNotificationCenter *localNotificationCenter = [UNUserNotificationCenter currentNotificationCenter];
localNotificationCenter.delegate = self;
//添加本地通知
[localNotificationCenter addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
}
}];
}
#pragma mark - UNUserNotificationCenterDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSLog(@"ViewController willPresentNotification");
NSLog(@"title : %@",notification.request.content.title);
NSLog(@"subtitle : %@",notification.request.content.subtitle);
NSLog(@"body : %@",notification.request.content.body);
NSLog(@"userInfo : %@",notification.request.content.userInfo);
//这个回调可以设置当收到通知后, 有哪些效果呈现(声音/提醒/数字角标)
completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
//
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
NSLog(@"applicationState %ld",(long)[UIApplication sharedApplication].applicationState);
switch ([UIApplication sharedApplication].applicationState) {
case UIApplicationStateActive:
//程序在前台时点击了alert会执行这里
break;
case UIApplicationStateInactive:
//应用程序运行在前台但不接收事件,程序从后台刚进入前台时的状态。当在后台点击alert进入前台时,会执行这里。
break;
case UIApplicationStateBackground:
//应用程序在后台,这种状态在这里捕获不到
break;
}
completionHandler();
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
以上代码将UNUserNotificationCenterDelegate放在了发送本地通知的类里面,不建议这样做,这样做的话当应用程序被杀死的情况下点击aler进入时,捕获不到通知。建议将代理设置到AppDelegate中,这样当程序杀死,点击alert进入时也可以捕获到通知内容。
二、远程推送
远程通知由服务器生成,由APNs发送到用户设备上。
需要开启远程推送功能并配置推送证书。
打开TARGET->Capabilities->push Notifications选项,徽章自动添加一个.entitlements的文件。还需要打开Background Modes下的Remote notifications。
1、请求授权,注册远程通知
以下是iOS10注册远程通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//远程推送对象
UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
//设置远程推送代理
notificationCenter.delegate = self;
//获取用户授权
[notificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay completionHandler:^(BOOL granted, NSError * _Nullable error) {
//回主线程
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted) {
//用户拒绝授权Coding...
} else {
//用户已授权,注册远程推送
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
});
}];
return YES;
}
如果报如下错误:
You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
需要打开BackGroundModel,勾选Remote notifications选项。
2、设置deviceToken
如下两个方法里回调注册结果
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken NS_AVAILABLE_IOS(3_0) {
NSString *token = [deviceToken description];
NSLog(@"description %@", token);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error NS_AVAILABLE_IOS(3_0) {
NSLog(@"didFailToRegisterForRemoteNotificationsWithError : %@",error);
}
如果注册成功会返回deviceToken,将这个token发送到我们自己的服务器。
3、服务器将消息发送至APNs
4、APNs通过设备标识将推送内容推送到设备
5、接收通知
iOS10以前
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0) {
NSLog(@"didReceiveRemoteNotification : %@",userInfo);
}
iOS10 Apple更新了推送方法,收到通知点击触发的方法- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler
已被弃用,使用新的方法如下:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
completionHandler(UIBackgroundFetchResultNewData);
NSDictionary * userInfo = response.notification.request.content.userInfo;
[self pushToNewsDetailViewController:userInfo];
}
iOS10通过代理接收通知消息,这和上面所说的本地通知是一样的,iOS10统一了本地通知和远程推送,使用UserNotifications框架简化了iOS推送的使用。
从以上几个步骤看如果要做一个推送的SDK,类似于极光推送这种,主要的工作还是在服务端,客户端需要将deviceToken发送给服务端,服务端根据苹果的需求做一些工作。
iOS10以前,程序在前台运行时,远程推送不会直接显示alert,需要捕获到通知内容,再做处理。程序在运行和非运行时候捕获通知的方法不同,需要单独处理。