【iOS】测试下远程推送接收通知的那些方法的调用顺序

前言

我们今天主要通过文档参考+实验的方法来得出远程推送接收通知的那些方法调用的一些结论。文章较长,可以直接跳到最后看结论。

与远程推送的有关的几个方法

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo NS_DEPRECATED_IOS(3_0, 10_0, "Use UserNotifications Framework's -[UNUserNotificationCenterDelegate willPresentNotification:withCompletionHandler:] or -[UNUserNotificationCenterDelegate didReceiveNotificationResponse:withCompletionHandler:] for user visible notifications and -[UIApplicationDelegate application:didReceiveRemoteNotification:fetchCompletionHandler:] for silent remote notifications");

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __OSX_AVAILABLE(10.14);

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __OSX_AVAILABLE(10.14) __TVOS_PROHIBITED;

方法1- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);
这个做iOS的人都知道,是App启动的时候会调用的方法,值得一说的是,这个方法有个launchOptions 参数,可以用来标识启动的原因。点击了通知弹框打开App的时候,这个方法是会带有推送的参数的。

方法2:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo NS_DEPRECATED_IOS(3_0, 10_0, "Use UserNotifications Framework's -[UNUserNotificationCenterDelegate willPresentNotification:withCompletionHandler:] or -[UNUserNotificationCenterDelegate didReceiveNotificationResponse:withCompletionHandler:] for user visible notifications and -[UIApplicationDelegate application:didReceiveRemoteNotification:fetchCompletionHandler:] for silent remote notifications");

一个非常古老的方法,在iOS7之前,推送主要是通过这个方法接收的。在iOS7之后就被下面的方法3替代了,而且在iOS10的时候这个方法已经被标为过时了,所以不建议使用。
方法3:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);
这个方法就比较重要了。iOS7的时候出来的方法,到iOS10之前都是称霸一方的大佬。当然由于静默推送的存在,直到现在仍然比较重要。除了能够完全替代方法2之外,还能处理推送唤醒的功能。关于推送唤醒(也叫做静默推送)这个功能比较复杂,本文不考虑这个功能。

方法4:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __OSX_AVAILABLE(10.14);
这个方法是iOS10推出的,属于UserNotifications框架中UNUserNotificationCenterDelegate代理协议的方法。主要是当App在前台的时候,收到推送通知之后调用。completionHandler回调可以控制是否显示alert、badge和sound。在iOS10之前,当App在前台的时候收到通知,是不会显示通知弹框的,但是在iOS10开始,实现了这个方法,我们可以选择是否显示通知弹框。比如在方法里面调用了completionHandler(UNNotificationPresentationOptionAlert);,当App在前台收到通知也会显示通知弹框。可操作性更高了。

方法5:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __OSX_AVAILABLE(10.14) __TVOS_PROHIBITED;
跟方法4配套的,同样是iOS10推出,属于UNUserNotificationCenterDelegate的方法。主要是当App在后台以及关闭状态下收到通知之后通过点击通知打开App的时候调用。

测试工具

上面大概介绍了几个方法的基本情况。但是实际上这几个推送方法的关系实在是剪不断理还乱。所以为了搞清楚这些关系,我做了一大波的测试。
工欲善其事必先利其器,我们需要几下几个东西:
1.一个可以用来测试的demo,由于远程推送是需要推送证书的,所以需要一个开发者账号,BundleID需要配置了开启推送,并配置相应的推送证书。
2.一个发送推送通知的工具,我用的是github上star数很高的NWPusher里面的Mac App,可以使用这个App来发送通知,很好用。话说,这App的图标简直无力吐槽。。。

Pusher App

3.一个log工具,需要记录我们手动启动的时候打印的log(因为无法直接从Xcode上看),并且能够方便查看。我就直接用我之前做过的一个浮窗log工具了。

4.测试推送需要使用真机,目前手上容易拿到的是两个手机,系统分别为iOS9.3.2和iOS12.3.1。由于主要分界线是iOS10及iOS10之前,所以这两个够用了。

开始测试

首先我们默认demo已经调通了推送,能够拿到deviceToken了。
推送的payload为{"aps":{"alert":"Testing.. (0)","badge":1,"sound":"default"}}(Puser这个App还很贴心地在每次推送完毕之后都会将Testing后面的数字加1,这样可以方便地辨认出那一次的通知)。静默推送的情况比较复杂,我们暂不测试。

上面的5个方法,我们只实现方法2(当然方法1一直都是要实现的,后面就不特别说明了)。

然后分别在App处于前台,App处于后台,App处于关闭状态的时候推送通知。以下为测试记录:

系统 App处于前台 App处于后台 App处于关闭状态
iOS9.3.2 不显示通知弹框,直接调用方法2 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法2 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1
iOS12.3.1 不显示通知弹框,直接调用方法2 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法2 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1

可以得出结论:
如果只实现方法2,不管是iOS10以下还是以上,推送通知到来时,
如果App处于前台,那么会直接调用方法2。
如果App处于后台,那么系统会显示通知提示框。如果我们不点击通知提示框,那什么都不会调用。如果点击了通知提示框,那么会打开App,并且会调用方法2。
当App在关闭状态,推送到来时,系统会弹出通知提示框。如果不点击通知提示框,那么什么都不会调用。如果点击了通知提示框,那么App会启动,将会调用方法1.方法1的launchOptions里面会有推送的参数。而方法2并不会调用。

接下测试的情况是只实现方法3

系统 App处于前台 App处于后台 App处于关闭状态
iOS9.3.2 不显示通知弹框,直接调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法3
iOS12.3.1 不显示通知弹框,直接调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法3

结论:
如果只实现方法3,不管是iOS10以下还是以上,推送通知到来时,
当App处于前台,跟只实现方法2时相同
当App处于后台,跟只实现方法2时相同
当App在关闭状态,推送到来时,系统会弹出通知提示框。如果不点击通知提示框,那么什么都不会调用。如果点击了通知提示框,那么App会启动,将会调用方法1.方法1的launchOptions里面会有推送的参数。接着将会调用方法3,方法3的参数跟方法1的launchOptions参数是相同的(其实不是完全相同,launchOptions参数会用UIApplicationLaunchOptionsRemoteNotificationKey为key的字典将推送传过来的参数包裹住,也就是将方法3收到的参数包裹住)。
也就是说,只实现方法2和只显示方法3的不同点在于,当App处于关闭状态时,点击通知提示框之后,App在调用了方法1之后,会调用方法3,而方法2却不会被调用。

同时实现方法2和方法3

系统 App处于前台 App处于后台 App处于关闭状态
iOS9.3.2 不显示通知弹框,直接调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法3
iOS12.3.1 不显示通知弹框,直接调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法3

结论:很明显,同时实现方法2和方法3,将会导致方法2被方法3覆盖,也就是说,方法2永远不会被调用。

只实现方法4和方法5
上文提过,方法4和方法5是配套使用的。所以现在要测试下只实现这两个方法的情况。先让方法4的参数设为0

系统 App处于前台 App处于后台 App处于关闭状态
iOS9.3.2 不会调用方法 显示通知,通知到来时弹出推送弹框,点击弹框打开App 显示通知,通知到来内框会打开App,点击弹框后会调用方法1
iOS12.3.1 不显示通知弹框,直接调用方法4 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法5 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法5

由于方法4和方法5是iOS10的时候才推出的,所以在iOS10以下的设备不生效。所以在iOS9.3.2设备上测试的时候这两个方法是无效的。在iOS10以上,这两个方法相当于将方法3拆分成了两个,在前台的时候调用方法4,在后台和关闭状态打开的时候调用方法5。

这里还需要测试一种情况,就是方法4收到推送之后,是可以弹出提示框的。我们把方法4的参数改为UNNotificationPresentationOptionAlert

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
    NSLogD(@"willPresentNotification notification=%@",notification);
    completionHandler(UNNotificationPresentationOptionAlert);
}
系统 App处于前台 App处于后台 App处于关闭状态
iOS12.3.1 调用方法4,显示通知,点击通知之后调用调用方法5 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法5 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法5

也就是说,App在前台的时候,我们能够让通知显示提示框。点击提示框之后依旧会走方法5.

再来测一下一波,当我们将上面说的所有方法都实现了,然后测一波

系统 App处于前台 App处于后台 App处于关闭状态
iOS9.3.2 不显示通知,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框打开App,调用方法3 显示通知,通知到来内框会打开App,点击弹框后会调用方法1,然后调用方法3
iOS12.3.1 不显示通知弹框,直接调用方法4 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法5 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法5

所以,跟推测中的结果一样,方法2不管怎样都不会调用,因为实现了方法3.在iOS10以下设备,方法3义不容辞地起作用了。在iOS10及以上设备,方法4和方法5都实现的情况下,优先调用方法4和方法5,而方法3将会被忽略掉。

到了这里,有没有想过,要是只实现方法5和方法3,或者只实现方法4和方法3,会怎么样呢?

我们只测试iOS10及以上设备,只实现方法5和方法3的情况

系统 App处于前台 App处于后台 App处于关闭状态
iOS12.3.1 不显示通知弹框,直接调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法5 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法5

只实现方法4和方法3的情况

系统 App处于前台 App处于后台 App处于关闭状态
iOS12.3.1 不显示通知弹框,直接调用方法4 显示通知,通知到来时弹出推送弹框,点击弹框后打开App,调用方法3 显示通知,通知到来时弹出推送弹框,点击弹框后会调用方法1,接着调用方法3

跟想象中一样,当方法4或方法5其中一个没实现的时候,备胎(方法3)就起作用了。

结论:

至此,我们就可以得出方法的调用顺序逻辑图了。

调用顺序流程图

推送通知接收方法的调用跟系统版本(以iOS10作为分界线)、App状态(前台、后台、关闭状态)、方法是否实现(只实现了某些方法或者所有方法都实现)有关。
方法的优先级为方法4和方法5 > 方法3 > 方法2。系统检测到有优先级高的方法就调用,就不会管优先级较低的方法了。
所以在iOS10及以上系统,系统会先查找[UNUserNotificationCenter currentNotificationCenter]是否有delegate,delegate是否实现了方法4和方法5,如果有,就调用。如果没有,就查找方法3,有就调用。如果没有,再去查找方法2。
iOS10以下系统没有UNUserNotification框架,所以系统会直接找方法3,如果有,就调用。如果没有,就去查找方法2进行处理。

参考

总结 iOS 接收远程推送的响应方法
苹果推送功能官方文档
远程推送didReceiveRemoteNotification代理方法调用详细说明

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容