修复账号登录后应用直接退出
系统的登录窗口在后台队列上回调结果,而回调闭包写在主线程隔离的方法里,Swift 6 在闭包入口检查执行器, 不在主线程就中止进程:选完 GitHub / Google / Apple 账号应用立即闪退,令牌还没换到,重开也没有登录状态。 回调改为在非隔离的上下文里创建,收到结果后再回到主线程处理;补了在后台队列调用回调的回归测试。
This commit is contained in:
1 parent
718d441fa9
commit
1022e4e541
5 files changed
+46
-4
No files matched your search
@@ -30,7 +30,7 @@ let package = Package(
|
||||
.testTarget(name: "HelperSharedTests", dependencies: ["HelperShared"]),
|
||||
.testTarget(name: "UpdatesTests", dependencies: ["Updates"]),
|
||||
.testTarget(name: "AccountSyncTests", dependencies: ["AccountSync"]),
|
||||
.testTarget(name: "OpenStatsUITests", dependencies: ["OpenStatsUI", "Metrics"]),
|
||||
.testTarget(name: "OpenStatsUITests", dependencies: ["OpenStatsUI", "Metrics", "AccountSync"]),
|
||||
.testTarget(name: "LocalizationTests", dependencies: ["Localization"]),
|
||||
],
|
||||
swiftLanguageModes: [.v6]
|
||||
|
||||
@@ -116,9 +116,8 @@ public final class SyncController {
|
||||
let verifier = PKCE.verifier()
|
||||
let url = client.loginURL(provider: provider, challenge: PKCE.challenge(for: verifier))
|
||||
let anchor = AnchorProvider(anchor: presentationAnchor)
|
||||
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: SyncClient.callbackScheme) { [weak self] callback, error in
|
||||
Task { @MainActor in self?.finishSignIn(callback: callback, error: error, verifier: verifier) }
|
||||
}
|
||||
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: SyncClient.callbackScheme,
|
||||
completionHandler: Self.completionHandler(for: self, verifier: verifier))
|
||||
session.presentationContextProvider = anchor
|
||||
// 保留浏览器里已登录的第三方账号,用户不必再输一次密码
|
||||
session.prefersEphemeralWebBrowserSession = false
|
||||
@@ -132,6 +131,15 @@ public final class SyncController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 登录结束时系统在它自己的 XPC 队列上回调,不在主线程。这个闭包必须在非隔离的上下文里创建:
|
||||
/// 直接写在 signIn 里会继承 @MainActor 隔离,Swift 6 在闭包入口检查执行器,发现不在主线程就中止进程
|
||||
/// (选完账号应用直接退出,令牌还没换到,重开也没有登录状态)
|
||||
nonisolated static func completionHandler(for controller: SyncController, verifier: String) -> @Sendable (URL?, Error?) -> Void {
|
||||
{ [weak controller] callback, error in
|
||||
Task { @MainActor in controller?.finishSignIn(callback: callback, error: error, verifier: verifier) }
|
||||
}
|
||||
}
|
||||
|
||||
public func cancelSignIn() {
|
||||
authSession?.cancel()
|
||||
authSession = nil
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AccountSync
|
||||
import Foundation
|
||||
@testable import Metrics
|
||||
import Testing
|
||||
@@ -387,3 +388,28 @@ private func isolatedDefaults() -> UserDefaults {
|
||||
#expect(ranking.ranked(limit: 2).map(\.id) == ["z", "x"])
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Suite struct SyncSignInTests {
|
||||
/// 系统在后台的 XPC 队列上回调登录结果。回调闭包如果带着主线程隔离,Swift 6 会在入口处中止进程,
|
||||
/// 表现为选完账号应用直接退出;这里在后台队列调用一次,能跑完并回到主线程处理就算通过
|
||||
@Test func completionHandlerMayRunOffMainThread() async {
|
||||
let controller = SyncController(settings: AppSettings(defaults: isolatedDefaults()), defaults: isolatedDefaults(),
|
||||
tokens: MemoryTokenStore())
|
||||
let handler = SyncController.completionHandler(for: controller, verifier: "verifier")
|
||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||
DispatchQueue.global().async {
|
||||
handler(nil, nil)
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
// 没有回调地址按失败处理;处理发生在主线程的下一个任务里,稍等一下
|
||||
for _ in 0..<100 where controller.phase == .idle {
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
}
|
||||
guard case .failed = controller.phase else {
|
||||
Issue.record("回调之后 phase 应为 failed,实际是 \(controller.phase)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user