发布于 

iOS APNs: 远程推送

概要

本篇博文, 你首先需要知道的内容:

1.了解过 Push.
2.阅读过 [iOS 后台模式] 这篇文章.
3.申请过苹果证书或者知道如何申请和制作证书.
4.iOS 开发基本知识.

你在这篇博文将会学到:

1.Push 的发展历程.
2.开源推送工具 NWPusher 的使用.
3.Push 的基本原理.

简介

APNs, 苹果推送通知服务. 全称是: Apple Push Notification Service.

推送指的是由 APNs 服务器、ProviderService、iOS 系统、App 构成的通讯系统,也是移动互联网与传统的 Web 最明显不同的.

官方有比较详细的文档介绍, 可以戳这里 官方文档 查看.

苹果的文档写的确实好, 但是作为程序员, 最重要的还是要结合理论去实践.

本篇结合自己的实践和对推送的理解, 跟大家分享一下推送相关的知识.

Push 发展历程

iOS 历经很多版本, 一直在优化或者说是进化推送相关的内容, 无论是从实用性和技术上来看, 推送是至关重要的.

看一下 push 发展历程

  • iOS 3 - 引入推送通知 UIApplication 的 registerForRemoteNotificationTypes 与 UIApplicationDelegate 的 application(:didRegisterForRemoteNotificationsWithDeviceToken:),application(:didReceiveRemoteNotification:)

  • iOS 4 - 引入本地通知 scheduleLocalNotification,presentLocalNotificationNow:, application(_:didReceive:)

  • iOS 5 - 加入通知中心页面

  • iOS 6 - 通知中心页面与 iCloud 同步

  • iOS 7 - 后台静默推送 application(_:didReceiveRemoteNotification:fetchCompletionHandle:)

  • iOS 8 - 重新设计 notification 权限请求,Actionable 通知 registerUserNotificationSettings(:),UIUserNotificationAction 与 UIUserNotificationCategory,application(:handleActionWithIdentifier:forRemoteNotification:completionHandler:) 等

  • iOS 9 - Text Input action,基于 HTTP/2 的推送请求 UIUserNotificationActionBehavior,全新的 Provider API 等

  • iOS 10- 支持Images, GIFs, Audio and Video类型, 并且有 Notification Service Extension 与 Notification Content Extension,可以实现推送数据在展示前进行下载更新、定制通知 UI, 并且统一了通知类型,具有时间间隔通知、地理位置通知和日历通知.

该系列博客共分为几个部分:

如果需要完整源码的, 可以通过邮件联系我(veritman@126.com), 后续完成后会上传到 github.

原理

先看官方的流程图, 如下所示:

1

该流程图, 主要说明的是自己 业务服务器(Provider) 推送消息到用户(Client APP) 的流程.

注意: 这里说的以及本文后面说的 业务服务器, 统一指的是可以向 APNs 发送推送消息的服务器.

下面这张图是比较完整的一张流程图, 自己画的, 凑合看吧!

1

大概流程, 我详细说一下.

1.iOS 设备启动后连接网络, 会与苹果服务器建立一个安全的长连接.
这个是系统维护的, 这也是推送的关键.
2.用户打开 app, 授权了推送通知的权限.
3.授权成功后, APNs 会将 deviceToken 返回给 iOS 终端.
4.终端将该 deviceToken 返回给指定的 APP.
5.APP 拿到 deviceToken 上传给我们自己的业务服务器.
6.业务服务器向 APNs 发送推送请求, 带上 deviceToken.
7.APNs 推送内容到指定的 iOS 终端.
8.iOS 终端将内容推送给用户.

关于 deviceToken 后面会讲.

关于 deviceToken

这里简单介绍一下 deviceToken.

deviceToken 是 NSData 类型的数据, 是苹果服务器根据 设备,证书等信息和一定算法生成的.
需要将这个 deviceToken 传送给我们的服务器端, 这样一个用户对象就绑定了一个 deviceToken.
当需要给用户推送消息, 通过自己的业务服务器, 找用户对应的 deviceToken 和要发送的推送内容, 发送到苹果的 APNs, 然后 APNs 将消息推送到该 deviceToken 对应的手机上.

关于 deviceToken 是否可变的问题

网上有些人说, deviceToken 是可变的, 有些人说, deviceToken 是不可变的, 我也不知道他们到底有没有实践过, 今天我要告诉大家的是, deviceToken 是可变的.

如卸载重装 app, deviceToken 会变. 我的设备 iphone6, iOS10.3.

下面是我实验的数据:

1
2
3
4
第一次安装运行得到的 deviceToken

<4e0f2928 5be0700c="" 296bf7f1="" 3b0837e4="" bc9da6d1="" 9fdb672e="" f87446be="" 1c098431="">
卸载后, 第一次安装运行得到的 deviceToken

这说明, deviceToken 是可变的.

代码实现

上面基本都是一些理论知识, 下面结合代码, 具体实现.

工程配置

在写代码之前, 需要配置一下工程.关于如何创建带有 push 功能的苹果证书的操作, 大家自行去网上学习, 这里不赘述.

不过这里要提醒一点, 创建的证书一定要和自己项目的 appid 保持一致, 否则无法推送.

这个 Demo 工程的名字是 MZPush.

安装好证书, 打开工程, 并能让工程识别到.

切换到 Target, 点击 Capabilities 选项.

步骤1: 配置 Push, 打开开关即可.

1

配置后台模式, 打开开关, 选中Remote notifications.

1

不过这一步, 不是必须的, 如果你不配置, 工程会有警告.我建议是选上, 在后面的博文中, 再仔细说说这个东西的好处.

警告信息如下:

1
2
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.

步骤2: 配置完这一步后,在 Info.plist 中可以看到多了一项内容:

1

步骤3: 配置工程最小兼容版本

1

因为我要兼容 iOS7, 所以在 Xcode8 中, 自己手动改为了 7.0.

步骤4: 关闭 Bitcode(可选操作)

1

步骤5: 配置支持 HTTP(可选操作)

在 Info.plist 中, 添加如下:

1

用户授权

询问用户授权的实现

该实现兼容了 iOS7.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (void)applyPushPermission {

UIApplication *application = [UIApplication sharedApplication];

if (MZSysVersion <= 7.0) {
UIRemoteNotificationType nType = UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound;
[application registerForRemoteNotificationTypes:nType];
}
else {
UIUserNotificationType nType = UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert;
UIUserNotificationSettings *nSettings = [UIUserNotificationSettings settingsForTypes:nType categories:nil];
[application registerUserNotificationSettings:nSettings];
}
}

关于用户授权, 分几种情况来看待.

情景一. 用户不允许 APP 推送, 即不授权.

Appdelegate 代理调用

1
2
3
4
5
6
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

MZLOG(@"App push. notificationSettings: %@", notificationSettings);

[application registerForRemoteNotifications];
}
1
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error

error 信息大概如下:

1
2
Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串"
UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}

情景二. 用户允许授权了, 又分为两种情况

1.使用具有 push 功能的证书

一定要有带有 push 功能的证书.

代理调用

1
2
3
4
5
6
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

MZLOG(@"App push. notificationSettings: %@", notificationSettings);

[application registerForRemoteNotifications];
}

这个方法大概在上面回调 1s 后才会调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

MZLOG(@"App push. deviceToken: %@", deviceToken);

// 格式化该数据
NSString *deviceTokenStr = [NSString stringWithFormat:@"%@", deviceToken];
deviceTokenStr = [deviceTokenStr stringByReplacingOccurrencesOfString:@"<" withString:@""];
deviceTokenStr = [deviceTokenStr stringByReplacingOccurrencesOfString:@">" withString:@""];
deviceTokenStr = [deviceTokenStr stringByReplacingOccurrencesOfString:@" " withString:@""];
MZLOG(@"App push. deviceToken string: %@", deviceTokenStr);

// 可以上传该 token 到自己的业务服务器
}

在 didRegisterForRemoteNotificationsWithDeviceToken 方法中可以得到 deviceToken 信息:

1

注意: 代码中将 NSData 的 deviceToken 转换为了 NSString 类型的数据类型.

2.使用一般的证书, 没有 push 功能的证书

这种情况和 情景一 一样.

推送

万事俱备, 只欠东风了.

今天没有准备搭建一个自己的业务服务器去推送, 可以使用推送工具来替代.

安装 NWPusher 这个工具, 可以进行推送测试.

安装成功后, 打开这个 Mac APP, 填写相关信息.

点击 push 即可推送.

1

在 Appdelegate 中的代理回调中, 可以打印推送内容.

1
2
3
4
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

MZLOG(@"App push. userInfo: %@", userInfo);
}

推送的内容, 如下图所示.

1

1
2
3
4
5
6
7
{
aps = {
alert = "Testing.. (6)";
badge = 1;
sound = default;
};
}

推荐

1.活久见的重构 - iOS 10 UserNotifications 框架解析

2.国内 90%以上的 iOS 开发者,对 APNs 的认识都是错的


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

veryitman