繁体   English   中英

SwiftUI 设计模式 - 如何从第一个结果中进行“第二阶段”API 调用

[英]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结构,因为VideoViewModelMainActor 我宁愿一次性下载所有内容,这样当用户进入子视图时,就没有加载时间了。

你是对的,一旦初始化就不能修改结构,因为它们的所有属性都是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM