push的主要工作流程:
- iOS设备连接网络后,会自动与APNS保持类似TCP的长连接,等待APNS推送消息。
- 应用启动时会注册消息推送,并且会从APNS获取到注册的唯一设备标识deviceToken,我们要把上传给应用的服务器。
- 在需要给应用推送消息时,Provider把push内容、接受push消息的deviceToken按APNS指定的格式打包好,发送给APNS;
- APNS接收到Provider发送的消息后,查找deviceToken,如果该设备已经和APNS建立了连接,则立即将该消息推送给该设备,如果设备不在线,则在设备下次连接到APNS后将消息推送给该设备。请注意苹果并不保证推送一定成功。
- 设备受到push消息后,iOS系统会根据SSL证书判断这个push消息是发给那个应用的,进而启动相应的客户端。
在上述过程中,有两个关键步骤需要自己处理:
- 获取设备的deviceToken并上传到Provider;
- Provider发送推送消息到APNS;
这两个步骤都需要苹果的证书授权。
iOS 10 中以前杂乱的和通知相关的 API 都被统一了,现在开发者可以使用独立的 UserNotifications.framework
来集中管理和使用 iOS 系统中通知的功能。在此基础上,Apple 还增加了撤回单条通知,更新已展示通知,中途修改通知内容,在通知中展示图片视频,自定义通知 UI 等一系列新功能,非常强大。
在程序启动后注册通知:
if (NSClassFromString(@"UNUserNotificationCenter")) {
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError *error) {
if (granted) {
#if !TARGET_IPHONE_SIMULATOR
UIApplication *application = [UIApplication sharedApplication];
[application registerForRemoteNotifications];
#endif
}
}];
return;
}
程序第一次启动调用这个方法时又弹出框:
要注意的是,一旦用户选择了不允许,之后程序里再次调用该方法也不会再进行弹窗,granted会一直是NO,用户必须自行前往系统的设置中为你的应用打开通知才能收到推动消息。
远程推送
一旦用户同意后,你就可以在应用中发送本地通知了。如果你想通过服务器发送远程通知的话,还需要多一个获取用户 token 的操作。你的服务器可以使用这个 token 向 Apple Push Notification 的服务器提交请求,然后 APNs 通过 token 识别设备和应用,将通知推给用户。
我们使用 UIApplication 的 registerForRemoteNotifications 来注册远程通知,在 AppDelegate 的 代理方法中获取用户 token:
UIApplication *application = [UIApplication sharedApplication];
[application registerForRemoteNotifications];
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
发送一个本地的推送:
UNMutableNotificationContent * content = [[UNMutableNotificationContent alloc] init];
content.title = @"test local push";
content.body = @"本地的推送消息";
UNTimeIntervalNotificationTrigger * trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
NSString * requestIdentifier = @"com.onevcat.usernotification.myFirstNotification";
UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"%@", error);
}
}];
调用这个方法以后把程序切换到后台(程序在前台时不会显示本地通知):
处理通知:
//当应用在前台收到通知时会调用这个方法,应用在后台或杀掉应用时是不会调用的
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
//在前台显示通知
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
//如果不想显示这个通知,可以
//completionHandler(0);
}
//这个代理方法会在用户与你推送的通知进行交互时被调用(不论在前台还是后台),包括用户通过通知打开了你的应用,或者出发了某个action,在这个方法的里必须要调用completionHandler来告诉系统你已经处理了通知
//UNNotificationResponses是一个几乎包含了通知所有信息的对象,可以获取到本地推送消息的userinfo,远程推送的payload内的内容也会出现在这个userInfo中。可以通过userInfo的内容来决定页面的跳转或者是进行其它操作。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)())completionHandler {
NSLog(@"%s", __func__);
completionHandler();
}
![response.jpg](http://upload-images.jianshu.io/upload_images/1311714-046082786218e449.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在iOS10里,本地通知和远程通知合二为一,区分本地通知跟远程通知的类是UNPushNotificationTrigger
这个类,UNPushNotificationTrigger
的类型是新增加的,通过它,我们可以得到一些通知的触发条件,在使用时,我们不应该直接使用这个类,应当使用它的子类。
- UNTimeIntervalNotificationTrigger 在特定的时间后出发本地通知
UNCalendarNotificationTrigger 在特定的date触发本地通知
UNLocationNotificationTrigger 在用户到达特定的地理位置时出发本地通知
UNPushNotificationTrigger 表示APNS通知
// iOS 10收到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
NSDictionary * userInfo = notification.request.content.userInfo;
UNNotificationRequest *request = notification.request; // 收到推送的请求
UNNotificationContent *content = request.content; // 收到推送的消息内容
NSNumber *badge = content.badge; // 推送消息的角标
NSString *body = content.body; // 推送消息体
UNNotificationSound *sound = content.sound; // 推送消息的声音
NSString *subtitle = content.subtitle; // 推送消息的副标题
NSString *title = content.title; // 推送消息的标题
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"iOS10 前台收到远程通知:%@", [self logDic:userInfo]);
}
else {
// 判断为本地通知
NSLog(@"iOS10 前台收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
}
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
}
通知的点击事件
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void(^)())completionHandler{
NSDictionary * userInfo = response.notification.request.content.userInfo;
UNNotificationRequest *request = response.notification.request; // 收到推送的请求
UNNotificationContent *content = request.content; // 收到推送的消息内容
NSNumber *badge = content.badge; // 推送消息的角标
NSString *body = content.body; // 推送消息体
UNNotificationSound *sound = content.sound; // 推送消息的声音
NSString *subtitle = content.subtitle; // 推送消息的副标题
NSString *title = content.title; // 推送消息的标题
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"iOS10 收到远程通知:%@", [self logDic:userInfo]);
}
else {
// 判断为本地通知
NSLog(@"iOS10 收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
}
// Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called.
completionHandler(); // 系统要求执行这个方法