Fork me on GitHub

利用 Background Fetch + UNUserNotificationCenter 实现伪推送

写在前面

Tips:UNUserNotificationCenter 从 iOS 10 开始支持,低于版本的本地推送请自行谷歌。

最近项目里面利用了第三方的 API,由干货集中营提供每日干货。由于没办法实现远程推送,考虑通过本地推送 + 后台获取的方式来实现【伪推送】,其实很多第三方的客户端,如微博的第三方客户端 Moke、奇点,HIPDA 论坛的第三方客户端,都是采用这样的方式进行推送的。如果有自己仿写第三方客户端的小伙伴,不妨试试这个方式来为你的 App 添加上推送(wei)功能。

Background Fetch

Background Fetch 是 Apple 官方在 iOS 7 推出的来后台刷新,提高用户体验的新方法。首先先参照苹果官方文档 Background Execution,后台获取干的事情就是在用户打开应用之前就使app有机会执行代码来获取数据,刷新UI,极大的提高了用户体验。

如何实现该功能呢?

开启后台获取

Targets - [Project] - Capabilities - Background Modes - Background fetch,即告诉系统应用开启后台获取的权限。

Background fetch

设置时间间隔

1
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

默认的值是 UIApplicationBackgroundFetchIntervalNever,这种情况下是不会使用后台获取。setMinimumBackgroundFetchInterval 的值除了提供的集合之外,任意的 NSTimeInterval 都可以设置。如果设置值,那么你能在应用中看到 后台应用刷新 是打开状态。

后台刷新

有人会问你把时间设置到最小,那么是不是应用时时刻刻都在刷新数据。答案显然不是!以 iOS 系统的脾气,当然不会让你这么为所欲为。那么,刷新的时机是什么时候?完全取决与系统。可能是你打开微信刷朋友圈的时候,帮你刷新一下;也可能单独为你唤醒设备来刷新一下。其实后来发现系统会记录我们的行为习惯,将获取刷新的时间调整到符合你行为习惯的时间。不过我们暂且不管获取的时机,应该根据应用的实际情况来进行时间间隔设置,毕竟用这个来做伪推送,那么推送刷新的及时性不是那么强,所以尽可能的长或许是个不错的选择。甚至于我在 App 中是跟用户讲清楚了机制,并且用户可以选择关闭或者开启,以及设置间隔时长。

实现

1
2
3
4
5
6
7
8
9
10
11
12
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if GankUserDefaults.isBackgroundEnable.value == false {
completionHandler(.noData)
return
}
GankBackgroundFetchService.shared.performFetchWithCompletionHandler { (result) in
SafeDispatch.async {
completionHandler(result)
}
}
}

AppDelegate 中的 -application:performFetchWithCompletionHandler: 方法来实现处理,你想通这边处理的数据、UI 和你在其他地方一样,唯一限制的就是时间不能超过 1 分钟,比如大图片的上传下载这种操作不要放在这边处理。CompletionHandler 会接受到 UIBackgroundFetchResult 闭包值,可选结果有 .noData.newData.failed 三种情况。

调试

关于调试,分为两种情况。一种是通过 Background Fetch 直接打开应用,另一种是应用在后台的时候模拟一次 Background Fetch 的情况。这边我只提供第二种测试方案,第一种相对麻烦,我后台通过真机测试了下。第二种情况跑通能且只能说明 Background Fetch 没有问题了。但是直接打开应用对数据的处理,确实需要第一种情况测试下,这边提供喵神的文章 WWDC 2013 Session笔记 - iOS7中的多任务 作为参考。

模拟后台运行,只需要选择 Xcode 的 Debug - Simulate Background Fetch,即可模拟一次。

Debug

UNUserNotificationCenter

接下来我们来聊聊推送的事情,那天我看了下 iOS 的推送,从 iOS 7 开始苹果为了整合推送的 API 和易用性,做了大量的调整,几乎一个版本一个花样,可以想象到激光推送这样的三方推送平台内心是崩溃的。

关于推送问题,这里不做过多的赘述,因为只是使用了最简单的本地推送,如果你对推送新特性有兴趣,同样推荐喵神的文章 活久见的重构 - iOS 10 UserNotifications 框架解析 作为参考,写的非常细致。

我这边直接讲解推送和后台刷新结合的问题,刚刚说到在 -application:performFetchWithCompletionHandler: 方法来实现处理。我给了一个 UserDefaults 讲是否是今天的日期存储下来,从来来决定是否推送。注意大家要根据实际情况来进行处理,我这边内容一天只会更新一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public func checkAuthorization() {
UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
SafeDispatch.async { [weak self] in
switch settings.authorizationStatus {
case .notDetermined:
self?.authorize()
case .authorized:
gankLog.debug("UserNotifications authorized")
case .denied:
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: nil)
}
}
})
}

通过 getNotificationSettings 来进行当前 Notication 状态的获取,从来进行操作。一共有三种状态 .notDetermined.authorized.denied,分别对应着仍未询问、已授权和已拒绝。可以看到我分别做了处理,未询问去进行授权提醒,而已经拒绝,可以通过方法跳转到应用的设置页面,即上面后台刷新那张图中进行状态的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
public func authorize() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, error) in
SafeDispatch.async { [weak self] in
self?.initAuthorization()
if granted {
gankLog.debug("UserNotifications authorized")
GankBackgroundFetchService.shared.turnOn()
} else {
gankLog.debug("UserNotifications denied")
}
}
}
}

通过 requestAuthorization 进行推送权限获取,注意这边获取如果用户拒绝了,那么不管你之后怎么调用这个方法,询问权限获取的弹窗都不会再出现。所以一定要注意询问的时期,最好不要再一打开 App 就进行询问,很容易被用户进行拒绝。

1
2
3
4
5
6
7
8
let content = UNMutableNotificationContent()
content.title = String.titleContentTitle
content.body = String.messageTodayGank
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let requestIdentifier = "gank update"
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

最后发送推送,这边给的是最基础的推送内容。

最后

到此,通过 Background Fetch + UNUserNotificationCenter 实现伪推送就都完成了。这两个部分我都封装了 Service:GankBackgroundFetchServiceGankNotificationService,希望对你有帮助。


版权声明



Ivan’s Blog by Ivan Ye is licensed under a Creative Commons BY-NC-ND 4.0 International License.
叶帆创作并维护的叶帆的博客博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Ivan’s Blog | 叶帆的博客博客( http://yeziahehe.com ),版权所有,侵权必究。

本文链接:http://yeziahehe.com/2017/07/19/Background_Fetch_and_UNUserNotificationCenter/