【问题标题】:Background mp3 download on iOS 14 in a swift ui app在 swift ui 应用程序中在 iOS 14 上下载背景 mp3
【发布时间】:2021-08-23 21:42:37
【问题描述】:

我正在创建一个快速 UI 广播流媒体应用,其中包含可以下载的过去剧集库。我希望用户能够开始下载然后锁定屏幕。目前,这会暂停正在进行的下载。我的下载功能:

func downloadFile(withUrl url: URL, andFilePath filePath: URL) {
        URLSession.shared
            .downloadTaskPublisher(for: url)
            .retry(4)
            .map(\.0)
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { [self] _ in
                      downloading = false
                      downloaded = true
                  },
                  receiveValue: { data in
                      do {
                          self.downloading = true
                          try FileManager.default.moveItem(atPath: data.path,
                                                           toPath: filePath.path)
                      } catch {
                          self.downloaded = false
                          print("Error: \(error.localizedDescription)")
                          print("an error happened while downloading or saving the file")
                      }
                  })
            .store(in: &networkSubscription)
    }

.downloadTaskPublisher(for: url) 是:

import Combine
import Foundation

public extension URLSession {
    /// Returns a publisher that wraps a URL session download task for a given
    /// URL.
    ///
    /// - Parameter url: The URL for which to create a download task.
    /// - Returns: A publisher that wraps a download task for the URL.
    func downloadTaskPublisher(for url: URL) -> DownloadTaskPublisher {
        DownloadTaskPublisher(session: self, request: URLRequest(url: url))
    }

    /// Returns a publisher that wraps a URL session download task for a given
    /// URL request.
    ///
    /// - Parameter request: The URL request for which to create a download task.
    /// - Returns: A publisher that wraps a download task for the URL request.
    func downloadTaskPublisher(for request: URLRequest) -> DownloadTaskPublisher {
        DownloadTaskPublisher(session: self, request: request)
    }
}

public struct DownloadTaskPublisher {
    fileprivate let session: URLSession
    fileprivate let request: URLRequest
}

extension DownloadTaskPublisher: Publisher {
    public typealias Output = (URL, URLResponse)
    public typealias Failure = Error

    public func receive<Subscriber>(subscriber: Subscriber)
        where
        Subscriber: Combine.Subscriber,
        Subscriber.Failure == Failure,
        Subscriber.Input == Output
    {
        let subscription = Subscription(subscriber: subscriber, session: session, request: request)
        subscriber.receive(subscription: subscription)
    }
}

private extension DownloadTaskPublisher {
    final class Subscription {
        private let downloadTask: URLSessionDownloadTask

        init<Subscriber>(subscriber: Subscriber, session: URLSession, request: URLRequest)
            where
            Subscriber: Combine.Subscriber,
            Subscriber.Input == Output,
            Subscriber.Failure == Failure
        {
            downloadTask = session.downloadTask(with: request, completionHandler: { url, response, error in

                guard let url = url, let response = response else {
                    subscriber.receive(completion: .failure(error!))
                    return
                }

                _ = subscriber.receive((url, response))
                subscriber.receive(completion: .finished)
            })
        }
    }
}

extension DownloadTaskPublisher.Subscription: Subscription {
    fileprivate func request(_: Subscribers.Demand) {
        downloadTask.resume()
    }

    fileprivate func cancel() {
        downloadTask.cancel()
    }
}

此下载功能会将剧集写入磁盘,但会在应用暂停时取消。

我想写一些类似的东西

func downloadFile(withUrl url: URL, andFilePath filePath: URL) {
        URLSession.init(configuration: URLSessionConfiguration.background(withIdentifier: "background.download.session"))
            .downloadTaskPublisher(for: url)
            .retry(4)
            .map(\.0)
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { [self] _ in
                      downloading = false
                      downloaded = true
                  },
                  receiveValue: { data in
                      do {
                          self.downloading = true
                          try FileManager.default.moveItem(atPath: data.path,
                                                           toPath: filePath.path)
                      } catch {
                          self.downloaded = false
                          print("Error: \(error.localizedDescription)")
                          print("an error happened while downloading or saving the file")
                      }
                  })
            .store(in: &networkSubscription)
    }

但是,这会引发运行时异常:libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.' terminating with uncaught exception of type NSException

我想使用组合成语在后台下载文件,避免将AppDelegate 附加到我的@main struct: App

【问题讨论】:

    标签: ios swift swiftui combine urlsession


    【解决方案1】:

    据我了解编译器抛出的错误,我很确定您需要像这样激活后台模式:

    我确实找到了raywenderlich.com 的详细教程,该教程几乎完全符合您的用例。也许它可以帮助您更深入地挖掘此功能?

    【讨论】:

    • 我认为你的意思是raywenderlich.com/… 不幸的是,这使用了我试图避免的AppDelegate 模式,最终这是一个选项,我的问题特别是询问是否有办法使用 swift ui AppCombine idioms 执行此操作。
    • 您必须使用 AppDelegate,因为就目前而言,据我所知,没有办法让后台任务正常工作。但是在 SwiftUI 中访问 AppDelegate 函数真的很容易。看看这个教程:hackingwithswift.com/quick-start/swiftui/…?
    • 一旦您将 AppDelegate 集成到您的 SwiftUI 应用程序生命周期中,就可以像使用 UIKit 应用程序一样轻松设置。 ?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-13
    • 1970-01-01
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    • 2015-05-21
    相关资源
    最近更新 更多