Upgrade to Pro — share decks privately, control downloads, hide ads and more …

一般的な通信でも使える
バックグラウンドURLSessionの活用方法 / How to us...

Avatar for Jierong Li Jierong Li
September 02, 2023

一般的な通信でも使える
バックグラウンドURLSessionの活用方法 / How to use background URLSession for general network data transfer tasks.

Avatar for Jierong Li

Jierong Li

September 02, 2023
Tweet

More Decks by Jierong Li

Other Decks in Programming

Transcript

  1. όοΫάϥ΢ϯυURLSessionͱ͸ URLSessionͱ͸ ▸ An object that coordinates a group of

    related, network data transfer tasks. ▸ ωοτϫʔΫ௨৴ʹ͓͍ͯෆՄܽͳ΋ͷɻ ▸ AlamofireͳͲͷωοτϫʔΫ௨৴ϥΠϒϥϦʔ͸͋͘·ͰURLSessionͷϥο ύʔɻ
  2. ׆༻ํ๏ BGAppRefreshTaskͷ୅ସ Suspend Foreground runtime Background runtime System Request App

    refresh for soon URLSession response Suspend App refresh task URLSession request
  3. ϝϦοτͱ׆༻ํ๏ ׆༻ํ๏ ϑΥΞάϥ΢ϯυͰ଴ͭ࣌ؒͷ࡟ݮʹ໾ཱͭɻ ▸ ৘ใͷࣄલऔಘ ▸ iOSDC Japan 2021ɿWidgetKitͰྑ͍ମݧΛ࡞Δʹ͸ ▸

    BGAppRefreshTaskͷ୅ସ ▸ λΠϜΞ΢τ΍தࢭͷճආ ▸ beginBackgroundTask(withName:expirationHandler:)ͷԆ௕ ▸ BGAppRefreshTaskͷԆ௕
  4. ׆༻ํ๏ BGAppRefreshTaskͷԆ௕ Background runtime System Suspend Suspend App refresh task

    URLSession request Background URLSession request Low runtime signal URLSession response
  5. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ public final class BackgroundSessionTaskManager: NSObject { public typealias

    CompletionHandler = (Result<(Data, HTTPURLResponse), Error>) -> Void private var session: URLSession! private var tasks: [Int: TaskWrapper] = [:] public init(sessionIdentifier: String) { super.init() self.session = .init( configuration: .background(withIdentifier: sessionIdentifier), delegate: self, delegateQueue: nil ) } func makeTask(with request: URLRequest, completionHandler: CompletionHandler?) -> URLSessionDownloadTask { precondition(request.url?.scheme == "https") let task = session.downloadTask(with: request) tasks[task.taskIdentifier] = TaskWrapper(task: task, completionHandler: completionHandler) return task } }
  6. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ private struct TaskWrapper { let wrappedValue: URLSessionDownloadTask let

    completionHandler: CompletionHandler? var fileReadResult: Result<Data, Error>? init( task: URLSessionDownloadTask, completionHandler: CompletionHandler? ) { self.wrappedValue = task self.completionHandler = completionHandler } }
  7. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ extension BackgroundSessionTaskManager: URLSessionDownloadDelegate { public func urlSession( _

    session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) { let key = downloadTask.taskIdentifier guard var task = tasks[key] else { return } task.fileReadResult = .init { try Data(contentsOf: location) } tasks[key] = task } }
  8. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ extension BackgroundSessionTaskManager: URLSessionTaskDelegate { public func urlSession( _

    session: URLSession, task: URLSessionTask, didCompleteWithError error: Error? ) { guard let task = tasks.removeValue(forKey: task.taskIdentifier), let completionHandler = task.completionHandler else { return } do { if let error = error { throw error } else if let response = task.wrappedValue.response as? HTTPURLResponse { let data = try task.fileReadResult?.get() ?? .init() completionHandler(.success((data, response))) } else { throw BackgroundSessionTaskError.invalidResponse } } catch { completionHandler(.failure(error)) } } }
  9. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ let task = manager.makeTask(with: request) { result in

    // ... } task.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60) task.resume()
  10. ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ UIKitͷ৔߹ public let manager = BackgroundSessionTaskManager(sessionIdentifier: "MySession") class AppDelegate:

    NSObject, UIApplicationDelegate { func application( _ application: UIApplication, handleEventsForBackgroundURLSession identifier: String ) async { await manager.waitUntilEventDeliveryFinished() } }
  11. ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ SwiftUIͷ৔߹ public let manager = BackgroundSessionTaskManager(sessionIdentifier: "MySession") @main struct

    PlaygroundApp: App { var body: some Scene { WindowGroup { ContentView() } .backgroundTask(.urlSession) { _ in await manager.waitUntilEventDeliveryFinished() } } }
  12. ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ Widget extensionͷ৔߹ struct MyWidget: Widget { let kind: String

    = "MyWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in WidgetEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("My Widget") .description("This is an example widget.") .backgroundTask(.urlSession) { identifier in let manager = BackgroundSessionTaskManager(sessionIdentifier: identifier) await manager.waitUntilEventDeliveryFinished() } } }
  13. ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ Widget extensionͷ৔߹ struct MyWidget: Widget { let kind: String

    = "MyWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in WidgetEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("My Widget") .description("This is an example widget.") .onBackgroundURLSessionEvents { identifier, completion in Task { let manager = BackgroundSessionTaskManager(sessionIdentifier: identifier) await manager.waitUntilEventDeliveryFinished() completion() } } } }
  14. ࣮૷ͷ۩ମྫ ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ extension BackgroundSessionTaskManager: URLSessionDelegate { public func urlSessionDidFinishEvents( forBackgroundURLSession

    session: URLSession ) { eventDeliveryCompletionHandler?() eventDeliveryCompletionHandler = nil } public func waitUntilEventDeliveryFinished() async { await withCheckedContinuation { eventDeliveryCompletionHandler = $0.resume } } }
  15. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ extension BackgroundSessionTaskManager: URLSessionDownloadDelegate { public func urlSession( _

    session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) { let key = downloadTask.taskIdentifier guard var task = tasks[key] else { return } task.fileReadResult = .init { try Data(contentsOf: location) } tasks[key] = task } }
  16. ࣮૷ͷ۩ମྫ DelegateͰ݁ՌΛड͚औΔ extension BackgroundSessionTaskManager: URLSessionDownloadDelegate { public func urlSession( _

    session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) { let key = downloadTask.taskIdentifier var task = tasks[key, default: .init(task: downloadTask, completionHandler: nil)] task.fileReadResult = .init { try Data(contentsOf: location) } tasks[key] = task } }
  17. ࣮૷ͷ۩ମྫ ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ extension BackgroundSessionTaskManager: URLSessionTaskDelegate { public func urlSession( _

    session: URLSession, task: URLSessionTask, didCompleteWithError error: Error? ) { guard let task = tasks.removeValue(forKey: task.taskIdentifier), let completionHandler = task.completionHandler else { return } do { if let error = error { throw error } else if let response = task.wrappedValue.response as? HTTPURLResponse { let data = try task.fileReadResult?.get() ?? .init() completionHandler(.success((data, response))) } else { throw BackgroundSessionTaskError.invalidResponse } } catch { completionHandler(.failure(error)) } } }
  18. ࣮૷ͷ۩ମྫ ΞϓϦ͕Ұ࣌ఀࢭɾऴྃ͞Εͨ৔߹ let task = tasks.removeValue(forKey: task.taskIdentifier) ?? .init(task: task,

    completionHandler: nil) let completionHandler = task.completionHandler ?? { result in var userInfo = [TaskDidCompleteNotification.UserInfoKey: Any]() userInfo[.description] = task.wrappedValue.taskDescription userInfo[.result] = result NotificationCenter.default.post( name: TaskDidCompleteNotification.name, object: self, userInfo: userInfo ) }
  19. ࣮૷ͷ۩ମྫ BGAppRefreshTaskͷ֦ு .backgroundTask(.appRefresh("WEATHER_DATA")) { let request = URLRequest(url: WEATHER_URL) await

    withTaskCancellationHandler { let (data, response) = try? await URLSession.shared.data(for: request) // ... } onCancel: { manager.makeTask(with: request, completionHandler: nil) .resume() } }
  20. ࢀߟ ▸ WWDC14: What’s New in Foundation Networking ▸ WWDC19:

    Advances in App Background Execution ▸ WWDC22: Efficiency awaits: Background tasks in SwiftUI ▸ Swift Package Manager: URLSessionHTTPClient ▸ URLSession: Common pitfalls with background download & upload tasks ▸ iOSDC Japan 2021ɿWidgetKitͰྑ͍ମݧΛ࡞Δʹ͸