[英]SwiftUI design pattern - How to make a "2nd-stage" API call from the result of the first one
这是在 iOS 15.5 上使用最新的 SwiftUI 标准。
我的 SwiftUI 应用程序中有这两个结构:
用户.swift
struct User: Codable, Identifiable, Hashable {
let id: String
let name: String
var socialID: String? // it's a var so I can modify it later
func getSocialID() async -> String {
// calls another API to get the socialID using the user's id
// code omitted
// example response:
// {
// id: "aaaa",
// name: "User1",
// social_id: "user_1_social_id",
// }
}
}
视频.swift
struct Video: Codable, Identifiable, Hashable {
let id: String
let title: String
var uploadUser: User
}
我的 SwiftUI 应用程序显示了一个视频列表,视频列表是从一个 API(我无法控制)获得的,响应如下所示:
[
{
id: "AAAA",
title: "My first video. ",
uploaded_user: { id: "aaaa", name: "User1" },
},
{
id: "BBBB",
title: "My second video. ",
uploaded_user: { id: "aaaa", name: "User1" },
},
]
我的视频的视图模型如下所示:
VideoViewModel.swift
@MainActor
class VideoViewModel: ObservableObject {
@Published var videoList: [Video]
func getVideos() async {
// code simplified
let (data, _) = try await URLSession.shared.data(for: videoApiRequest)
let decoder = getVideoJSONDecoder()
let responseResult: [Video] = try decoder.decode([Video].self, from: data)
self.videoList = responseResult
}
func getSocialIDForAll() async throws -> [String: String?] {
var socialList: [String: String?] = [:]
try await withThrowingTaskGroup(of: (String, String?).self) { group in
for video in self.videoList {
group.addTask {
return (video.id, try await video.uploadedUser.getSocialId())
}
}
for try await (userId, socialId) in group {
socialList[userId] = socialId
}
}
return socialList
}
}
现在,我希望填写User
结构的socialID
字段,我必须使用每个用户的 ID 从另一个 API 获取该字段。 每个用户的响应如下所示:
{
id: "aaaa",
name: "User1",
social_id: "user_1_social_id",
}
现在,获取信息的唯一可行方法似乎是使用withThrowingTaskGroup()
并为我正在使用的每个用户调用getSocialID()
,然后我可以返回一个包含每个用户的所有socialID
信息的字典,然后该字典可以在 SwiftUI 视图中使用。
但是,有没有办法让我填写User
结构中的socialID
字段而无需使用单独的字典? 一旦 JSON 解码器初始化视频列表,我似乎无法修改videoList
中每个Video
中的User
结构,因为VideoViewModel
是MainActor
。 我宁愿一次性下载所有内容,这样当用户进入子视图时,就没有加载时间了。
你是对的,一旦初始化就不能修改结构,因为它们的所有属性都是let
变量; 但是,您可以修改VideoViewModel
中的videoList
,从而省去Dictionary
。
@MainActor
class VideoViewModel: ObservableObject {
@Published var videoList: [Video]
func getVideos() async {
// code simplified
let (data, _) = try await URLSession.shared.data(for: videoApiRequest)
let decoder = getVideoJSONDecoder()
let responseResult: [Video] = try decoder.decode([Video].self, from: data)
self.videoList = try await Self.getSocialIDForAll(in: responseResult)
}
private static func updatedWithSocialID(_ user: User) async throws -> User {
return User(id: user.id, name: user.name, socialID: try await user.getSocialID())
}
private static func updatedWithSocialID(_ video: Video) async throws -> Video {
return Video(id: video.id, title: video.title, uploadUser: try await updatedWithSocialID(video.uploadUser))
}
static func getSocialIDForAll(in videoList: [Video]) async throws -> [Video] {
return try await withThrowingTaskGroup(of: Video.self) { group in
videoList.forEach { video in
group.addTask {
return try await self.updatedWithSocialID(video)
}
}
var newVideos: [Video] = []
newVideos.reserveCapacity(videoList.count)
for try await video in group {
newVideos.append(video)
}
return newVideos
}
}
}
使用视图模型对象不是 SwiftUI 的标准,它更像是一种 UIKit 设计模式,但实际上使用内置的子视图控制器更好。 SwiftUI 的设计是围绕使用值类型来防止对象典型的一致性错误,所以如果你使用对象,那么你仍然会遇到这些问题。 View 结构被设计为主要的封装机制,因此您可以使用 View 结构及其属性包装器获得更大的成功。
因此,为了解决您的用例,您可以使用@State
属性包装器,它为 View 结构(具有值语义)提供引用类型语义,就像一个对象一样,使用它来保存生命周期与屏幕上的 View 匹配的数据. 对于下载,您可以通过task(id:)
修饰符使用 async/await。 这将在视图出现时运行任务,并在id
参数更改时取消并重新启动它。 结合使用这 2 个功能,您可以执行以下操作:
@State var socialID
.task(id: videoID) { newVideoID in
socialID = await Social.getSocialID(videoID: newViewID)
}
父View
应该有一个获取视频信息的任务。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.