发布于 

iOS APNs: 本地推送

概括

iOS APNs: 远程推送 说过远程推送(RemotePush).

今天说说本地推送, 本地推送也就是平时所说的 LocalPush.

该系列博客:

业务场景

在具体实现之前, 先说一下业务场景.

假如你的 APP 具有 IM(即时通讯) 功能, 这个时候, 你可以根据连接来判断用户是否在线, 如果不在线, 可以使用远程推送将推送内容告知用户.如果在线, 可以通过 IM 把内容告诉 APP, APP 收到这个消息后, 可以使用本地推送告知给用户.

据说 APNs 每天要处理的推送在亿级别, 所以如果可以使用 LocalPush 完成的业务, 我建议大家还是不要使用远程推送, 况且苹果并不一定保证远程推送一定成功, 如果网络或者 APNs 压力大, 推送也会延时.

给苹果减少点压力吧, 哈哈!

再说一个业务场景, 你的 APP 在后台运行时间快到要被系统挂起的时候了, 你可以发送一个 LocalPush 来提示用户或者刺激用户, 再次将 APP 拉回前台运行. 这样可以保证 APP 可以正常运行了.

上面说的第二个业务场景, 是今天例子的基础, 你也可以根据自己的实际业务场景来使用 LocalPush 功能.

LocalPush 简介

LocalPush 允许 APP 向用户发送通知, 对于用户来说, 就跟远程推送是一样的, 基本没有感知, 开发者也不希望用户有感知.

推送效果图:

1

注意: 如果你的 APP 在前台, 发送 LocalPush 是不会要上述效果的.
如果在前台, 可以使用自定义的弹框来提示用户.

LocalPush 同样需要用户授权推送的权限, 否则也无法发送成功.这个跟远程推送是一样的.

另外, LocalPush 需要 APP 在后台没有被挂起的情况下, 才能发送, 否则无法启用.

发送 LocalPush 的一个好处是不需要用户连接网络, 这个是区别于远程推送的, 因为远程推送必须要求用户连接网络的.很多单机游戏或者弱联网的游戏, 发送的推送都是 LocalPush, 而非远程推送.

在阅读下面内容之前, 建议大家先看看 iOS 后台模式 这篇文章.本篇也是基于这个为基础的.

实现

这个示例, 实现的场景如下:

用户打开 APP, 授权了推送的权限, 用户试玩一会之后, 将 APP 退到了后台, APP 监听退到后台之后, 延时一定时间发送 LocalPush 给用户.

AppDelegate.m 中使用后台模式

关于 MZBackgroundTask 的实现, 附录给出.

1
2
3
4
- (void)applicationDidEnterBackground:(UIApplication *)application {

[[MZBackgroundTask sharedTask] startTask];
}

ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void)viewDidLoad {

[super viewDidLoad];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
- (void)onDidEnterBackground:(NSNotification *)notification {

MZLOG(@"App Background. Enter onDidEnterBackground.");

// 等待 6s 后, 这个时间可以根据具体情况去修改, 这里只是模拟
int delta = 6;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delta * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

MZLOG(@"App Background. Enter onDidEnterBackground diapatch.");

if (UIApplicationStateBackground == [UIApplication sharedApplication].applicationState) {
UILocalNotification *notification = [UILocalNotification new];
notification.alertBody = @"走, 去high吧!";
notification.soundName = UILocalNotificationDefaultSoundName;
// 应用图标上面显示的数字
notification.applicationIconBadgeNumber = notification.applicationIconBadgeNumber + 1;
// 可以自定义数据
notification.userInfo = @{@"user_info_key": @"user_info_value_json_str"};

[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
else {
// 显示自定义弹框等
}
});
}

将 APP 点击 Home 键退到后台, 6s 后可以看到推送的效果.

点击推送的提示框, 再次打开了 APP, 会执行 AppDelegate 中的方法, 如下:

1
2
3
4
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {

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

打印出来的 notification, 如下图所示:

1

其中, user_info 是我们自定义的数据部分.

附录

MZBackgroundTask 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#import "MZBackgroundTask.h"

@implementation MZBackgroundTask

+ (instancetype)sharedTask {

static MZBackgroundTask *_task;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_task = [[self alloc] init];
});

return _task;
}

- (void)startTask {

if (![self _checkSupportBackgroundTask]) {
MZLOG(@"BackgroundTask. Current device don't support backgroundTask.");
return;
}

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier taskId;

/// 申请后台执行
/// 注意: 在iOS7和该版本前,后台可以用下面的的方式在后台存活5-10分钟,在iOS8及后,最多存活3分钟
{
taskId = [application beginBackgroundTaskWithName:NSStringFromClass([self class]) expirationHandler:^{

MZLOG(@"BackgroundTask. BackgroundTask is Over. The remained time: %f", application.backgroundTimeRemaining);

[application endBackgroundTask:taskId];

taskId = UIBackgroundTaskInvalid;
}];
}

if (UIBackgroundTaskInvalid == taskId) {

MZLOG(@"BackgroundTask. Apply backgroundTask failed.");

return;
}

/// 可以监控后台任务剩余的时间, 针对业务可以去处理
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

while (true) {
// 剩余可以后台执行的时间
NSTimeInterval remainedTime = application.backgroundTimeRemaining;
MZLOG(@"BackgroundTask. The remained time: %f", remainedTime);

if (remainedTime < 2) {
// 可以告诉其他业务, 后台任务即将结束了
break;
}

// 睡眠(延时)1s
[NSThread sleepForTimeInterval:1.f];
}

/// 这里可以做一些清除工作
{
// clean up
}

[application endBackgroundTask:taskId];

taskId = UIBackgroundTaskInvalid;
});
}
}

#pragma mark - Private.

/**
* 当前设备是否支持后台任务.
*
* @return YES, 支持后台任务. 否则, 不支持后台任务.
*/
- (BOOL)_checkSupportBackgroundTask {

SEL sel = @selector(isMultitaskingSupported);
BOOL supportBTask = [[UIDevice currentDevice] respondsToSelector:sel];

return supportBTask;
}

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

veryitman