菜鸟教程小白 发表于 2022-12-11 20:11:44

ios - 来自 iOS 后台 URLSession 的 REST 请求使用 APNs


                                            <p><h2>2018 年 5 月 25 日更新:</h2>

<p>在阅读 Rob 的回答后,我将 <code>datatask</code> 替换为 <code>downloadTask</code> : <a href="https://stackoverflow.com/a/44140059/4666760" rel="noreferrer noopener nofollow">https://stackoverflow.com/a/44140059/4666760</a> 。当应用程序在后台运行时,它仍然不起作用。 </p>

<hr/>

<p>你好</p>

<p>我需要一些有关 iOS 后台任务的帮助。我想使用 Apple 推送通知服务 (APNs) 在后台唤醒我的应用程序,以便它可以对我的服务器进行简单的 RESTful API 调用。当应用程序在前台而不是在后台时,我可以让它工作。我想我对 <code>URLSession</code> 的配置做错了,但我不知道。应用程序和服务器的完整代码位于下面链接的我的仓库中。请克隆它并做任何你喜欢的事情 - 我只是想要你的帮助:) </p>

<p> <a href="https://github.com/knutvalen/ping" rel="noreferrer noopener nofollow">https://github.com/knutvalen/ping</a> </p>

<p>在 <code>AppDelegate.swift</code> 中应用监听远程通知:</p>

<pre><code>class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    // MARK: - Properties

    var window: UIWindow?

    // MARK: - Private functions

    private func registerForPushNotifications() {
      UNUserNotificationCenter.current().delegate = self
      UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
            guard granted else { return }
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
      }
    }

    // MARK: - Delegate functions

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -&gt; Bool {
      Login.shared.username = &#34;foo&#34;
      registerForPushNotifications()
      return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      let tokenParts = deviceToken.map { data -&gt; String in
            return String(format: &#34;%02.2hhx&#34;, data)
      }
      let token = tokenParts.joined()
      os_log(&#34;AppDelegate application(_:didRegisterForRemoteNotificationsWithDeviceToken:) token: %@&#34;, token)
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
      os_log(&#34;AppDelegate application(_:didFailToRegisterForRemoteNotificationsWithError:) error: %@&#34;, error.localizedDescription)
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: , fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -&gt; Void) {
      os_log(&#34;AppDelegate application(_:didReceiveRemoteNotification:fetchCompletionHandler:)&#34;)
            if let aps = userInfo[&#34;aps&#34;] as? {
            if aps[&#34;content-available&#34;] as? Int == 1 {
                RestController.shared.onPing = { () in
                  RestController.shared.onPing = nil
                  completionHandler(.newData)
                  os_log(&#34;AppDelegate onPing&#34;)
                }

                RestController.shared.pingBackground(login: Login.shared)
//                RestController.shared.pingForeground(login: Login.shared)
            }
      }
    }

    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -&gt; Void) {
      RestController.shared.backgroundSessionCompletionHandler = completionHandler
    }
}
</code></pre>

<p><code>RestController.swift</code> 处理带有后台配置的 <code>URLSession</code>:</p>

<pre><code>class RestController: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {

    // MARK: - Properties

    static let shared = RestController()
    let identifier = &#34;no.qassql.ping.background&#34;
    let ip = &#34;http://123.456.7.89:3000&#34;
    var backgroundUrlSession: URLSession?
    var backgroundSessionCompletionHandler: (() -&gt; Void)?
    var onPing: (() -&gt; ())?

    // MARK: - Initialization

    override init() {
      super.init()
      let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
      configuration.isDiscretionary = false
      configuration.sessionSendsLaunchEvents = true
      backgroundUrlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    }

    // MARK: - Delegate functions

    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
      DispatchQueue.main.async {
            if let completionHandler = self.backgroundSessionCompletionHandler {
                self.backgroundSessionCompletionHandler = nil
                completionHandler()
            }
      }
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
      if let error = error {
            os_log(&#34;RestController urlSession(_:task:didCompleteWithError:) error: %@&#34;, error.localizedDescription)
      }
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
      do {
            let data = try Data(contentsOf: location)
            let respopnse = downloadTask.response
            let error = downloadTask.error

            self.completionHandler(data: data, response: respopnse, error: error)
      } catch {
            os_log(&#34;RestController urlSession(_:downloadTask:didFinishDownloadingTo:) error: %@&#34;, error.localizedDescription)
      }
    }

    // MARK: - Private functions

    private func completionHandler(data: Data?, response: URLResponse?, error: Error?) {
      guard let data = data else { return }

      if let okResponse = OkResponse.deSerialize(data: data) {
            if okResponse.message == (&#34;ping_&#34; + Login.shared.username) {
                RestController.shared.onPing?()
            }
      }
    }

    // MARK: - Public functions

    func pingBackground(login: Login) {
      guard let url = URL(string: ip + &#34;/ping&#34;) else { return }
      var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
      request.setValue(&#34;application/json&#34;, forHTTPHeaderField: &#34;Content-Type&#34;)
      request.httpMethod = &#34;POST&#34;
      request.httpBody = login.serialize()

      if let backgroundUrlSession = backgroundUrlSession {
            backgroundUrlSession.downloadTask(with: request).resume()
      }
    }

    func pingForeground(login: Login) {
      guard let url = URL(string: ip + &#34;/ping&#34;) else { return }
      var request = URLRequest(url: url)
      request.setValue(&#34;application/json&#34;, forHTTPHeaderField: &#34;Content-Type&#34;)
      request.httpMethod = &#34;POST&#34;
      request.httpBody = login.serialize()

      URLSession.shared.dataTask(with: request) { (data, response, error) in
            return self.completionHandler(data: data, response: response, error: error)
      }.resume()
    }
}
</code></pre></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>通过在 <code>info.plist</code> 中添加 <code>App 提供 IP 语音服务</code> 作为所需的后台模式,并使用 PushKit 处理 APNs 负载,我能够做我想做的事。我的存储库中提供了 SSCCE(示例):</p>

<p> <a href="https://github.com/knutvalen/ping" rel="noreferrer noopener nofollow">https://github.com/knutvalen/ping</a> </p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - 来自 iOS 后台 URLSession 的 REST 请求使用 APNs,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/50497207/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/50497207/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - 来自 iOS 后台 URLSession 的 REST 请求使用 APNs