尝试更新代码以刷新 OAuth 中的令牌后,我无法刷新视图。
我的屏幕:
struct MyScreen: View {
@ObservedObject var viewModel = MyViewModel()
var body: some View {
NavigationStack {
List {
ForEach(viewModel.activeElements) { element in
NavigationLink(destination: ElementView(elementId: 1)) {
ElementRowView(element: element)
}
}
}
.refreshable {
await viewModel.fetchElements()
}
.onReceive(viewModel.$elements) { _ in
print("Elements updated")
}
}
.task {
await viewModel.fetchElements()
}
}
}
我的视图模型:
class MyViewModel : ObservableObject {
@Published var elements: [Element] = []
@Published var error: APIError?
var activeElements: [Element] {
elements.filter { $0.name != "" }
}
func fetchElements() async {
print("fetching")
do {
let response = try await ElementsAction().call()
print("assigning")
self.elements = response
self.error = nil
} catch let apiError as APIError {
self.error = apiError
} catch {
self.error = .unknown
}
}
}
元素动作:
struct ElementsAction {
let path = "/api/v1/elements"
let method: HTTPMethod = .get
// var parameters: LoginRequest
func call() async throws -> [Element] {
do {
let data = try await APIRequest<EmptyRequest, Element>.call(
path: path,
method: .get,
authorized: true
)
let response = try JSONDecoder().decode([Element].self, from: data)
return response
} catch {
throw error
}
}
}
和 API 请求:
class APIRequest<Parameters: Encodable, Model: Decodable> {
static func call(
scheme: String = Config.shared.scheme,
host: String = Config.shared.host,
path: String,
method: HTTPMethod,
authorized: Bool = false,
queryItems: [URLQueryItem]? = nil,
parameters: Parameters? = nil,
retryCount: Int = 0
) async throws -> Data {
if !NetworkMonitor.shared.isReachable {
throw APIError.noInternet
}
var components = URLComponents()
components.scheme = scheme
components.host = host
components.path = path
if let queryItems = queryItems {
components.queryItems = queryItems
}
guard let url = components.url else {
throw APIError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
if let parameters = parameters {
request.httpBody = try? JSONEncoder().encode(parameters)
}
if authorized, let token = Auth.shared.getAccessToken() {
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.response
}
print("------------------")
print("on request: \(request) (\(httpResponse.statusCode))")
switch httpResponse.statusCode {
case 200...299:
print("on request: \(request) (\(httpResponse.statusCode)) - return data")
return data
case 401 where retryCount < 1 && authorized:
print("on request: \(request) (\(httpResponse.statusCode)) - 401")
let success = await refreshToken()
if success {
let responseData = try await call(scheme: scheme, host: host, path: path, method: method, authorized: authorized, queryItems: queryItems, parameters: parameters, retryCount: retryCount + 1)
print("2nd returns data")
return responseData
} else {
throw APIError.unauthorized
}
case 401 where retryCount >= 1 && authorized:
throw APIError.unauthorized
default:
throw APIError.response
}
}
private static func refreshToken() async -> Bool {
do {
let response = try await RefreshTokenAction(
parameters: RefreshTokenRequest(
refresh_token: Auth.shared.getRefreshToken() ?? ""
)
).call()
print("updating token")
await MainActor.run {
Auth.shared.setCredentials(
accessToken: response.accessToken,
refreshToken: response.refreshToken
)
}
return true
} catch {
return false
}
}
}
当不需要调用refreshToken()时,此代码可以正常工作 - 只有一个请求。
但是,在更新刷新令牌时,它无法正常工作 - 在这种情况下,它会正确设置内容,因此下一个请求可以工作,但在该请求上,分配元素时不会更新元素。我使用当前的打印方法进行了调查,这是一个日志:
Elements updated
fetching
on request: https://myapp.com/api/v1/elements (401)
on request: https://myapp.com/api/v1/elements (401) - 401
on request: https://myapp.com/oauth/token (200)
on request: https://myapp.com/oauth/token (200) - return data
updating token
Elements updated <------ RECIEVE BEFORE FINISHING ALL REQUESTS
on request: https://myapp.com/api/v1/elements (200)
on request: https://myapp.com/api/v1/elements (200) - return data
2nd returns data
assigning <--------- IT DOESN'T UPDATE @Published elements
我尝试了多种方法,我猜问题可能出在并发上,但我完全不知道如何解决这个问题。谁能告诉我这里有什么问题以及如何解决它?
当您使用 async/await 时,您可以删除旧的合并
ObservableObject
和 @Published
代码,正如您所发现的,它们可能容易出错。 .task
自动为您提供类似引用的生命周期,因此不需要对象。您只需将异步函数更改为返回值的标准类型,例如
func fetchElements() async throws -> [Element] {
然后你可以为你的结果添加一个@State:
@State var elements: [Element] = []
并使用如下:
.task {
elements = try? await fetchElements()
}
为了进一步改进架构,您可以将异步函数放入控制器结构中,并创建一个环境密钥来保存它。然后你可以在预览时用模拟替换控制器,例如
@Environment(\.myController) var myController
...
.task {
elements = try? await myController.fetchElements()
}