From fd9976c4b9d8b28896ae13dc127c100637680244 Mon Sep 17 00:00:00 2001 From: joywayer Date: Tue, 23 Jun 2026 21:34:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E8=BF=9B=E5=BA=A6=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E6=94=B9=20session-level=20delegate=EF=BC=88ad-hoc=20?= =?UTF-8?q?=E4=B8=8D=E8=A7=A6=E5=8F=91=20didWriteData=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Source/Resource/LobbyZipUpgrader.swift | 33 +++++++++++-------- .../Source/Resource/SubGameDownloader.swift | 32 +++++++++++------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/ylgamehall/Source/Resource/LobbyZipUpgrader.swift b/ylgamehall/Source/Resource/LobbyZipUpgrader.swift index cefb417..4a215eb 100644 --- a/ylgamehall/Source/Resource/LobbyZipUpgrader.swift +++ b/ylgamehall/Source/Resource/LobbyZipUpgrader.swift @@ -28,18 +28,17 @@ public actor LobbyZipUpgrader { case stagingMoveFailed(any Error) } - private let session: URLSession + public init() {} - public init(session: URLSession = LobbyZipUpgrader.defaultSession) { - self.session = session - } - - /// 默认 URLSession:下载用 60s 超时(zip 大约 11 MB,4G 网络约 10s)。 - nonisolated public static var defaultSession: URLSession { + /// URLSession 配置:60s 单请求 / 300s 总体(zip 约 11 MB,4G 约 10s)。 + /// 每次 upgradeIfNeeded 用此 config 临时建 session,绑 download delegate 接收 + /// didWriteData 进度回调;ad-hoc delegate(`session.download(from:delegate:)`) + /// 不会触发 URLSessionDownloadDelegate 的 download-specific 方法,必须走 session 级。 + nonisolated private static var defaultConfig: URLSessionConfiguration { let cfg = URLSessionConfiguration.default cfg.timeoutIntervalForRequest = 60 cfg.timeoutIntervalForResource = 300 - return URLSession(configuration: cfg) + return cfg } /// 比较远端 game_version 与本地 version.xml。需要升级则下载 + 解压 + 原子 rename。 @@ -61,15 +60,23 @@ public actor LobbyZipUpgrader { return .noop } - // 1. 下载到 tmp(URLSession 默认管理临时位置),delegate 上报 0…1 进度 + // 1. 下载到 tmp(URLSession 默认管理临时位置),delegate 上报 0…1 进度。 + // delegate 必须在 URLSession init 时绑定,否则 didWriteData 不会被调用 + // (async API `download(from:delegate:)` 的 ad-hoc delegate 只收 task-level 回调)。 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 response: URLResponse do { - (downloadedURL, response) = try await session.download( - from: zipURL, - delegate: progressDelegate - ) + (downloadedURL, response) = try await session.download(from: zipURL) } catch { throw UpgradeError.downloadFailed(error) } diff --git a/ylgamehall/Source/Resource/SubGameDownloader.swift b/ylgamehall/Source/Resource/SubGameDownloader.swift index 27311a2..68c662e 100644 --- a/ylgamehall/Source/Resource/SubGameDownloader.swift +++ b/ylgamehall/Source/Resource/SubGameDownloader.swift @@ -36,17 +36,17 @@ public actor SubGameDownloader { case stagingMoveFailed(any Error) } - private let session: URLSession + public init() {} - public init(session: URLSession = SubGameDownloader.defaultSession) { - self.session = session - } - - nonisolated public static var defaultSession: URLSession { + /// URLSession 配置:60s 单请求 / 300s 总体(zip 体量约 1-10 MB)。 + /// 每次 ensureReady 用此 config 临时建 session,绑 download delegate 接收 + /// didWriteData 进度回调;ad-hoc delegate(`session.download(from:delegate:)`) + /// 不会触发 URLSessionDownloadDelegate 的 download-specific 方法,必须走 session 级。 + nonisolated private static var defaultConfig: URLSessionConfiguration { let cfg = URLSessionConfiguration.default cfg.timeoutIntervalForRequest = 60 cfg.timeoutIntervalForResource = 300 - return URLSession(configuration: cfg) + return cfg } /// 确保 `{Caches}/{request.gameDir}/{request.gameStart}/index.html` 已就绪。 @@ -72,15 +72,23 @@ public actor SubGameDownloader { throw DownloadError.badDownloadURL(request.downloadURL) } - // 1. 下载到 tmp + // 1. 下载到 tmp。delegate 必须在 URLSession init 时绑定,否则 + // URLSessionDownloadDelegate.didWriteData 不会被调用(async API + // `download(from:delegate:)` 的 ad-hoc delegate 只收 task-level 回调)。 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 response: URLResponse do { - (downloadedURL, response) = try await session.download( - from: zipURL, - delegate: progressDelegate - ) + (downloadedURL, response) = try await session.download(from: zipURL) } catch { throw DownloadError.downloadFailed(error) }