You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift(Xcode)中如何利用后台应用刷新设置本地通知并检测HTML变更

iOS后台刷新与通知实现方案

一、配置后台刷新基础权限

  • 打开Xcode项目的Signing & Capabilities面板,添加Background Modes能力,勾选Background fetch选项,这是后台刷新的核心权限。
  • 在代码中设置最小刷新间隔(iOS13+在SceneDelegate,iOS12及以前在AppDelegate):
    // SceneDelegate.swift
    func sceneDidBecomeActive(_ scene: UIScene) {
        // 设置为5分钟(300秒),系统会根据设备状态、用户习惯动态调整实际刷新频率
        UIApplication.shared.setMinimumBackgroundFetchInterval(300)
    }
    
    注:系统不会严格遵循设定的时间,过于频繁的刷新请求会被限流,甚至禁止后台任务。

二、实现后台刷新核心逻辑

1. 处理后台刷新回调

iOS13+使用SceneDelegate的performFetchWithCompletionHandler方法,iOS12及以前用AppDelegate的同名方法,在这个回调里完成网络请求、内容对比、通知触发的完整流程。

2. 完整代码示例

import UIKit
import Alamofire
import CryptoKit
import UserNotifications

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func sceneDidBecomeActive(_ scene: UIScene) {
        // 提前申请通知权限
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
            if granted {
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }
        // 设置后台刷新间隔
        UIApplication.shared.setMinimumBackgroundFetchInterval(300)
    }

    // 后台刷新回调方法
    func scene(_ scene: UIScene, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        // 从Keychain获取登录凭证(敏感信息禁止存在UserDefaults)
        guard let authToken = KeychainHelper.getAuthToken() else {
            completionHandler(.failed)
            return
        }

        // 用Alamofire请求最新的transcriptHTML
        AF.request("https://your-api-domain/transcript",
                   headers: ["Authorization": "Bearer \(authToken)"])
        .responseString { [weak self] response in
            guard let self = self else {
                completionHandler(.failed)
                return
            }

            switch response.result {
            case .success(let newHTML):
                let defaults = UserDefaults.standard
                // 用哈希值对比(比直接对比大HTML字符串高效)
                let oldHash = defaults.string(forKey: "transcriptHash")
                let newHash = newHTML.sha256()

                if newHash != oldHash {
                    // 更新本地存储
                    defaults.set(newHTML, forKey: "transcript")
                    defaults.set(newHash, forKey: "transcriptHash")
                    // 触发本地通知
                    self.sendUpdateNotification()
                    completionHandler(.newData)
                } else {
                    completionHandler(.noData)
                }
            case .failure(let error):
                print("后台请求失败: \(error.localizedDescription)")
                completionHandler(.failed)
            }
        }
    }

    // 发送本地通知
    private func sendUpdateNotification() {
        let content = UNMutableNotificationContent()
        content.title = "内容更新提醒"
        content.body = "你的Transcript已更新"
        content.sound = .default

        let request = UNNotificationRequest(identifier: UUID().uuidString,
                                            content: content,
                                            trigger: nil)
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("通知添加失败: \(error)")
            }
        }
    }
}

// 字符串SHA256哈希扩展
extension String {
    func sha256() -> String {
        guard let data = self.data(using: .utf8) else { return "" }
        let hash = SHA256.hash(data: data)
        return hash.map { String(format: "%02x", $0) }.joined()
    }
}

// 简易Keychain工具类(存储敏感凭证)
enum KeychainHelper {
    static func getAuthToken() -> String? {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: "AuthToken",
            kSecReturnData: kCFBooleanTrue!,
            kSecMatchLimit: kSecMatchLimitOne
        ]
        var data: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &data)
        guard status == errSecSuccess, let tokenData = data as? Data else {
            return nil
        }
        return String(data: tokenData, encoding: .utf8)
    }
}

三、关键优化与注意事项

  • 敏感信息存储:登录token必须存在Keychain,禁止用UserDefaults,避免被非法获取。
  • 内容对比优化:如果后端支持,建议让接口返回版本号或更新时间戳,仅在版本变化时再下载完整HTML,大幅节省流量和后台任务耗时。
  • 后台刷新限制:iOS系统会根据设备电量、APP使用频率调整刷新频率,不要依赖严格的5-10分钟触发逻辑。
  • 调试方法:在Xcode中通过Debug > Simulate Background Fetch可以直接触发后台刷新,快速验证逻辑。
  • 通知权限:必须提前向用户申请通知权限,否则无法发送本地通知。

内容的提问来源于stack exchange,提问作者Arav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 15:24:19