下载进度回调改 session-level delegate(ad-hoc 不触发 didWriteData)

LobbyZipUpgrader 和 SubGameDownloader 之前用 `session.download(from:delegate:)`
的 iOS 15+ async API,把 DownloadProgressDelegate 作为 ad-hoc delegate 传入。
该 API 的 delegate 参数实际只收 URLSessionTaskDelegate 回调,**不会**触发
URLSessionDownloadDelegate.didWriteData,所以 0…1 进度从未上报,splash 进度条
一直停在 0,只有末尾兜底的 onProgress(1.0) 跑一下,看起来"一出现就 100%"。

改为每次下载临时新建一个 URLSession,把 progressDelegate 在 init 时绑到
session 级别(带独立 OperationQueue),下载结束 defer invalidateAndCancel
释放。delegate 现在能收到 didWriteData,真实字节比例上报,进度条 0→1 平滑。

副作用:删除 actor 字段 `session` 与可注入的 `init(session:)`,改用静态
`defaultConfig: URLSessionConfiguration` 复用超时配置(无外部调用方使用过自定义
session init)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-23 21:34:38 +08:00
co-authored by Claude Opus 4.7
parent 57d4d93781
commit fd9976c4b9
2 changed files with 40 additions and 25 deletions
@@ -28,18 +28,17 @@ public actor LobbyZipUpgrader {
case stagingMoveFailed(any Error) case stagingMoveFailed(any Error)
} }
private let session: URLSession public init() {}
public init(session: URLSession = LobbyZipUpgrader.defaultSession) { /// URLSession 60s / 300s zip 11 MB4G 10s
self.session = session /// upgradeIfNeeded config session download delegate
} /// didWriteData ad-hoc delegate`session.download(from:delegate:)`
/// URLSessionDownloadDelegate download-specific session
/// URLSession 60s zip 11 MB4G 10s nonisolated private static var defaultConfig: URLSessionConfiguration {
nonisolated public static var defaultSession: URLSession {
let cfg = URLSessionConfiguration.default let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 60 cfg.timeoutIntervalForRequest = 60
cfg.timeoutIntervalForResource = 300 cfg.timeoutIntervalForResource = 300
return URLSession(configuration: cfg) return cfg
} }
/// game_version version.xml + + rename /// game_version version.xml + + rename
@@ -61,15 +60,23 @@ public actor LobbyZipUpgrader {
return .noop return .noop
} }
// 1. tmpURLSession delegate 01 // 1. tmpURLSession delegate 01
// delegate URLSession init didWriteData
// async API `download(from:delegate:)` ad-hoc delegate task-level
let progressDelegate = DownloadProgressDelegate(onProgress: onProgress) let progressDelegate = DownloadProgressDelegate(onProgress: onProgress)
let opQueue = OperationQueue()
opQueue.maxConcurrentOperationCount = 1
let session = URLSession(
configuration: Self.defaultConfig,
delegate: progressDelegate,
delegateQueue: opQueue
)
defer { session.invalidateAndCancel() }
let downloadedURL: URL let downloadedURL: URL
let response: URLResponse let response: URLResponse
do { do {
(downloadedURL, response) = try await session.download( (downloadedURL, response) = try await session.download(from: zipURL)
from: zipURL,
delegate: progressDelegate
)
} catch { } catch {
throw UpgradeError.downloadFailed(error) throw UpgradeError.downloadFailed(error)
} }
@@ -36,17 +36,17 @@ public actor SubGameDownloader {
case stagingMoveFailed(any Error) case stagingMoveFailed(any Error)
} }
private let session: URLSession public init() {}
public init(session: URLSession = SubGameDownloader.defaultSession) { /// URLSession 60s / 300s zip 1-10 MB
self.session = session /// ensureReady config session download delegate
} /// didWriteData ad-hoc delegate`session.download(from:delegate:)`
/// URLSessionDownloadDelegate download-specific session
nonisolated public static var defaultSession: URLSession { nonisolated private static var defaultConfig: URLSessionConfiguration {
let cfg = URLSessionConfiguration.default let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 60 cfg.timeoutIntervalForRequest = 60
cfg.timeoutIntervalForResource = 300 cfg.timeoutIntervalForResource = 300
return URLSession(configuration: cfg) return cfg
} }
/// `{Caches}/{request.gameDir}/{request.gameStart}/index.html` /// `{Caches}/{request.gameDir}/{request.gameStart}/index.html`
@@ -72,15 +72,23 @@ public actor SubGameDownloader {
throw DownloadError.badDownloadURL(request.downloadURL) throw DownloadError.badDownloadURL(request.downloadURL)
} }
// 1. tmp // 1. tmpdelegate URLSession init
// URLSessionDownloadDelegate.didWriteData async API
// `download(from:delegate:)` ad-hoc delegate task-level
let progressDelegate = DownloadProgressDelegate(onProgress: onProgress) let progressDelegate = DownloadProgressDelegate(onProgress: onProgress)
let opQueue = OperationQueue()
opQueue.maxConcurrentOperationCount = 1
let session = URLSession(
configuration: Self.defaultConfig,
delegate: progressDelegate,
delegateQueue: opQueue
)
defer { session.invalidateAndCancel() }
let downloadedURL: URL let downloadedURL: URL
let response: URLResponse let response: URLResponse
do { do {
(downloadedURL, response) = try await session.download( (downloadedURL, response) = try await session.download(from: zipURL)
from: zipURL,
delegate: progressDelegate
)
} catch { } catch {
throw DownloadError.downloadFailed(error) throw DownloadError.downloadFailed(error)
} }