推送对App的重要性不言而喻,是每个iOS开发者必修的技能。网上的资料对于初学者并不友好(至少对于我来讲),其中有许多坑。而且因为要配置证书,只能真机调试等,学起来更是难上加难。这篇文章是从刚开始接触推送的起点写起。最近有点忙,有些地方没有细究,只是暂时知道了能实现什么,而且贴出了一些文章的连接,方便进一步研究,若是有错误的地方请指出来。html
这篇先探究苹果原生态推送,下篇再写极光推送和IMiOS —— 极光推送和极光IM。bash
苹果原生态推送服务器
推送知识点及疑惑的地方微信
推送消息调用方法的时机,以及系统能作到的更多骚扩展。markdown
iOS推送分为本地推送和远程推送。app
它们都须要用户受权。本地不须要联网和证书,远程须要联网和证书。ide
本地推送好比闹钟,能够设置推送时间,是否重复推送,通知内容等。oop
远程推送,就多坑了。本文主要探讨远程推送。post
在项目 target 中,打开Capabilitie -> Push Notifications,并会自动在项目中生成 .entitlement 文件。打开Capablitie -> Background Modes -> Remote notifications。fetch
iOS8.0以上在AppDelegate.m中
#ifdef NSFoundationVersionNumber_iOS_9_x_Max #import <UserNotifications/UserNotifications.h> #endif 复制代码
而且遵循协议UNUserNotificationCenterDelegate
(该协议是iOS10.0+用,后面再提到)。
做用:苹果APNs服务器找到对应的设备和App。
原理:经过设备UDID和App的Bundle Identifier生成deviceToken。这样APNs服务器接受到信息就知道转发给哪一个设备的哪一个App。
作法:
在程序首次调用如下方法时,会请求推送权限。
首先用户要容许App发送通知。而后App发起注册,最终注册成功回调方法得到deviceToken。这个过程一般是在程序刚启动的时候。
注意:发起注册的方法在iOS8.0后更新了一次,10.0后又更新了一次。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self replyPushNotificationAuthorization:application]; return YES; } - (void)replyPushNotificationAuthorization:(UIApplication *)application{ if (IOS10_OR_LATER) { //iOS 10 later UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; //必须写代理,否则没法监听通知的接收与点击事件 center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!error && granted) { //用户点击容许 NSLog(@"注册成功"); }else{ //用户点击不容许 NSLog(@"注册失败"); } }]; // 能够经过 getNotificationSettingsWithCompletionHandler 获取权限设置 //以前注册推送服务,用户点击了赞成仍是不一样意,以及用户以后又作了怎样的更改咱们都无从得知,如今 apple 开放了这个 API,咱们能够直接获取到用户的设定信息了。注意UNNotificationSettings是只读对象哦,不能直接修改! [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { NSLog(@"========%@",settings); //打印结果 ========<UNNotificationSettings: 0x1740887f0; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, alertSetting: NotSupported, carPlaySetting: Enabled, alertStyle: Banner> }]; }else if (IOS8_OR_LATER){ //iOS 8 - iOS 10系统 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]; [application registerUserNotificationSettings:settings]; }else{ //iOS 8.0系统如下 [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound]; } //注册远端消息通知获取device token [application registerForRemoteNotifications]; } 复制代码
对以上注册方法说几句
categories
,这里为了方便写为nil。其做用是针对推送作拓展功能。这里给出图,让读者暂时知道有什么用,还能自定义这个界面,文章后面会再提到。iOS8的方法,能直接输入。
iOS10的方法,能增长按钮事件。
registerForRemoteNotificationTypes:
,不然不管如何也不能注册成功。None:不显示任何东西。
Alert:弹出提示框。
Badge:App小红点。
Sound:发出声音或震动。
复制代码
实测极光推送,Alert< Badge < Sound。即只设置了Badge仍是会默认带上Alert,设置了Sound会默认带上Badge和Sound。不过看接口是按位或操做,仍是乖乖写JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound
。
注册结果回调
#pragma mark - 获取device Token //获取DeviceToken成功 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{ //解析NSData获取字符串 //我看网上这部分直接使用下面方法转换为string,你会获得一个nil(别怪我不告诉你哦) //错误写法 //NSString *string = [[NSString alloc] initWithData:deviceToken encoding:NSUTF8StringEncoding]; //正确写法 NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""]; NSLog(@"deviceToken===========%@",deviceString); } //获取DeviceToken失败 - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{ NSLog(@"[DeviceToken Error]:%@\n",error.description); } 复制代码
获取deviceToken存在本地,等用户登陆后,和帐号id一块儿发送到服务器绑定起来。如总体流程图那样。
iOS10.0以前
// 处理本地推送
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}
复制代码
调用时机:App处于前台收到推送;在iOS7后,开启了 Remote Notification,App处于后台收到推送。
// 处理远程消息 // 方法二是在iOS 7以后新增的方法,能够说是 方法一 的升级版本,若是app最低支持iOS 7的话能够不用添加 方法一了。 //- (void)application:(UIApplication *)application //didReceiveRemoteNotification:(NSDictionary *)userInfo //{ // NSLog(@"userinfo:%@",userInfo); // NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]); //} // 其中completionHandler这个block能够填写的参数UIBackgroundFetchResult是一个枚举值。主要是用来在后台状态下进行一些操做的,好比请求数据,操做完成以后,必须通知系统获取完成,可供选择的结果有 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { NSLog(@"userinfo:%@",userInfo); NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]); completionHandler(UIBackgroundFetchResultNewData); // typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) { // UIBackgroundFetchResultNewData, // UIBackgroundFetchResultNoData, // UIBackgroundFetchResultFailed // } } 复制代码
这里解释一下userInfo
,详细点进连接看。
{ "aps" : { "alert" : { "title" : "iOS远程消息,我是主标题!-title", "subtitle" : "iOS远程消息,我是主标题!-Subtitle", "body" : "Dely,why am i so handsome -body" }, "badge" : "2" } } 做者:Dely 连接:https://www.jianshu.com/p/c58f8322a278 來源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。 复制代码
这里补充一点东西。
远程推送通知,分为 普通推送/后台推送/静默推送 3 种类型。
在推送通知-后台通知/静默通知文章里,有深刻介绍。
简单的说,userInfo
中的aps
中能够设置一个键值对"content-available" : 1
,其表明后台推送。在后台推送基础上不设置badge、sound、aleert的静默推送能够静悄悄让App更新数据。
这三种类型对应调用的方法会有所不一样,文末会说。
iOS10.0以后,新增两个方法。原来的方法仍是须要实现的,各自的调用时机不同。
// App处于前台接收到通知 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { NSLog(@"iOS10 收到远程通知"); }else { // 判断为本地通知 NSLog(@"iOS10 收到本地通知"); } // 在前台默认不显示推送,若是要显示,就要设置如下内容 // 微信设置里-新消息通知-微信打开时-声音or振动 就是该原理 // 须要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型能够设置 completionHandler(UNNotificationPresentationOptionBadge| UNNotificationPresentationOptionSound| UNNotificationPresentationOptionAlert); } 复制代码
我的以为这里iOS 10的方法didRecieve有歧义,实际是点击推送才调用。
// 点击通知后会调用 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { completionHandler(UIBackgroundFetchResultNewData); NSDictionary *userInfo = response.notification.request.content.userInfo; //程序关闭状态点击推送消息打开 能够在App启动方法的launchOptions获知 if (self.isLaunchedByNotification) { //TODO } else{ //前台运行 if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { //TODO } //后台挂起时 else{ //TODO } //收到推送消息手机震动,播放音效 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); AudioServicesPlaySystemSound(1007); } //设置应用程序角标数为1 [UIApplication sharedApplication].applicationIconBadgeNumber = 1; // 此处必需要执行下行代码,否则会报错 completionHandler(); } 复制代码
这些方法还有坑,笔者放到本文最后的调用时机里再说。
到这里已经把整个流程大体描绘出来了。
这里补充一些知识点,和提出一些疑惑的地方。
补充点小知识点,应该能更好地理解原理。
APNs与设备保持长链接。(APNs服务器真猛,信息量这么大)
基于上一点,不难理解,推送自己是 iOS 系统的行为。因此在 App 没有运行(没有在前台也没有在后台)的时候:
1⃣️仍然可以推送及接收(通知中心通知、顶部横幅、刷新 App 右上角的小圆点即 badge [如下简称角标] 等都会由系统来控制和展现)。
2⃣️(收到推送时,是没法在 App 的代码中获取到通知内容的。由于沙盒机制,此时 App 的任何代码都不可能被执行。)这个观点是在另外一篇文章看到的,但笔者实测发现并非这么回事。处于后台收到推送是会执行代码的。
因此就算App关掉后台应用刷新,照样能接收到推送。
以微信为例,若是你打开了后台应用刷新,那么当你收到新消息提醒以后,你打开微信,未读信息已经在那了(若是网速没问题的话);而当你收到新消息提示,打开微信后,消息才刚刚开始收取。
iOS的推送,内容的大小最大是4KB。 A发送很长的文字给B,是发到服务器,由服务器暂存数据,通知B来取。B收到推送后去服务器下载。(图片、语音等相似)
iOS10之后,收到推送能在后台下载附件,限制:图片是10M Video是50M。 有兴趣的能够去研究下,www.jianshu.com/p/f5337e8f3…
APNs服务器会向App服务器返回一个发送结果。(虽然这是后台的事,仍是稍微了解下)
怎么针对特定的人群发出推送。例如群聊。
是把群id发到服务器,而后服务器查出成员各自的id对应的deviceToken发出推送吗?极光有个根据Tag推送,之后了解到会更新文章。
若是该帐号并不在线,那这条消息要怎么处理?
把消息暂时存在后台服务器,等待有设备登陆这个帐号后绑定userId和deviceToken后,发起推送。
点击推送默认只是打开App。但也能增长一些操做,例如跳转到某个界面、微信直接回复等。
因此最后深刻了解推送还能怎么玩,以及解释App在前台、挂起、关闭状态下对应的方法。
1.若是App被kill掉的状况下,点击了推送,那么首先要启动App,会调用- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
。 参数launchOptions
能分辨出是经过点击App icon仍是点击推送打开App。
在iOS7.0以前,处于前台收到推送,调用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
。
在iOS7.0~10.0-,处于前台收到推送,调用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
。
坑了笔者的是,在iOS7.0+(包括10.0后),收到后台推送或静默推送,调用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
。
3.iOS10.0+就功能就丰(皮)富了。
在前台时收到推送,调用- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
。
因此,App处于前台时收到后台推送或静默推送的话,在iOS10+会调用两个方法,注意不要重复处理。- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
和- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
。若是你还点击了推送,那么还会调用下面这个方法。。。
点击推送,调用- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler
。
在iOS10后,针对这三个方法,为了避免重复处理同一条推送的内容。笔者建议尽可能同一模块的推送,在这各个方法中不要相同逻辑处理。
例如QQ,若是采用后台或静默推送,就只在静默中处理;
不是静默推送。在前台收到,就红点显示,通知栏不要显示。若是不在前台,通知栏显示,点击后可跳转到对应聊天框。
以前上面提过的category
对推送作拓展。由于项目暂时没有这个需求,而求最近有点忙,就先不钻研这部分了。找到了一篇好文章,介绍了iOS10的推送按钮操做。
iOS10.0后能对推送进行查找和删除 利用场景,例如微信撤回后,本来的推送将被改成"用户"撤回了一条信息。
// Notification requests that are waiting for their trigger to fire //获取未送达的全部消息列表 - (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler; //删除全部未送达的特定id的消息 - (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers; //删除全部未送达的消息 - (void)removeAllPendingNotificationRequests; // Notifications that have been delivered and remain in Notification Center. Notifiations triggered by location cannot be retrieved, but can be removed. //获取已送达的全部消息列表 - (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED; //删除全部已送达的特定id的消息 - (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED; //删除全部已送达的消息 - (void)removeAllDeliveredNotifications __TVOS_PROHIBITED; 做者:Dely 连接:https://www.jianshu.com/p/c58f8322a278 來源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。 复制代码