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

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

Jierong Li
September 02, 2023

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

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Ͱྑ͍ମݧΛ࡞Δʹ͸