使用 ObservableObject 时如何保持数据引用

How to keep reference of data when using ObservableObject

我是 Swiftui 的新手,我很难理解如何在渲染视图时正确保留在 ObservableObject 中创建的数据?或者可能采用完全不同的方法来解决问题?

更具体地说,它是关于在 List() 的每一行中获取 HTTP 数据。

目前,它在呈现父视图时使 HTTP 调用过于频繁,这会导致重新加载所有行。

同样的问题可以在这里找到:

public class VideoFetcher: ObservableObject {
    @Published var video: VideoResponse?
    @Published var coverImage: UIImage?
    @Published var coverImageLoading = false
    @Published var categories: String?
    @Published var loading = false
    @Published var error = false

    func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
        self.loading = true

        Video.findBySlug(
            mediaItemSlug: mediaItemSlug,
            broadcasterSlug: broadcasterSlug,
            successCallback: {video -> Void in                
                self.video = video
                self.loading = false

                self.setCategories()
                self.loadCoverImage()
            },
            errorCallback: {(error, _) -> Void in
                self.loading = false
                self.error = true
            })
    }

    func loadCoverImage() {
        guard self.video!.coverImageUrl != "" else {
            return
        }

        self.coverImageLoading = true

        let downloader = ImageDownloader()
        let urlRequest = URLRequest(url: URL(string: self.video!.coverImageUrl)!)
        let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))

        downloader.download(urlRequest, filter: filter) { response in
            if case .success(let image) = response.result {
                self.coverImage = image
                self.coverImageLoading = false
            }
        }
    }

    func setCategories() {
        if (self.video!.broadcaster.categories.count > 0) {
            let categoryNames = self.video!.broadcaster.categories.map { category in
                return category.name == "" ? "(no name)" : category.name
            }

            self.categories = categoryNames.joined(separator: " • ");
        }
    }
}

List() 行:

struct VideoCard: View {
    @ObservedObject var fetcher = VideoFetcher()
    ...
    init() {
        // Causes reload each render
        self.fetcher.load()
    }

    var body: some View {
        ...
        .onAppear {
            // Loads that on appear but fetcher.video is nil after view re-rendered because load() wasn't called
            self.fetcher.load()
        }
    }
}

谢谢,克里斯。我以为我在架构层面上做错了什么,但我添加了缓存并解决了我的问题。

import Alamofire
import AlamofireImage
import Cache

public class VideoFetcher: ObservableObject {
    @Published var video: VideoResponse?
    @Published var coverImage: UIImage?
    @Published var coverImageLoading = false
    @Published var broadcasterImage: UIImage?
    @Published var categories: String?
    @Published var loading = false
    @Published var error = false

    func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
        let videoCache = try? AppCache.video!.object(forKey: mediaItemSlug)

        if (videoCache != nil) {
            self.video = videoCache
            self.setCategories()
            self.loadCoverImage()

            return
        }

        self.loading = true

        Video.findBySlug(
            mediaItemSlug: mediaItemSlug,
            broadcasterSlug: broadcasterSlug,
            successCallback: {video -> Void in
                try? AppCache.video!.setObject(video, forKey: mediaItemSlug)

                self.video = video
                self.loading = false

                self.setCategories()
                self.loadCoverImage()
                self.loadBroadcasterImage()
            },
            errorCallback: {(error, _) -> Void in
                self.loading = false
                self.error = true
            })
    }

    func loadCoverImage() {
        let coverImageUrl = self.video!.coverImageUrl

        guard coverImageUrl != "" else {
            return
        }

        let urlRequest = URLRequest(url: URL(string: coverImageUrl)!)
        let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: coverImageUrl)

        if (cachedImage != nil) {
            self.coverImage = cachedImage
            return
        }

        self.coverImageLoading = true

        let downloader = ImageDownloader(imageCache: AppCache.image!)
        let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))

        downloader.download(urlRequest, filter: filter) { response in
            if case .success(let image) = response.result {
                AppCache.image!.add(image, for: urlRequest, withIdentifier: coverImageUrl)

                self.coverImage = image
                self.coverImageLoading = false
            }
        }
    }

    func loadBroadcasterImage() {
        let broadcasterImage = self.video!.broadcaster.avatarImageUrl

        guard broadcasterImage != "" else {
            return
        }

        let urlRequest = URLRequest(url: URL(string: broadcasterImage)!)
        let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: broadcasterImage)

        if (cachedImage != nil) {
            self.broadcasterImage = cachedImage
            return
        }

        let downloader = ImageDownloader(imageCache: AppCache.image!)
        let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 16, height: 16))

        downloader.download(urlRequest, filter: filter) { response in
            if case .success(var image) = response.result {
                image = image.af.imageRoundedIntoCircle()
                AppCache.image!.add(image, for: urlRequest, withIdentifier: broadcasterImage)
                self.broadcasterImage = image
            }
        }
    }

    func setCategories() {
        let categories = self.video!.broadcaster.categories

        if (categories.count > 0) {
            let categoryNames = categories.map { category in
                return category.name == "" ? "(no name)" : category.name
            }

            self.categories = categoryNames.joined(separator: " • ");
        }
    }
}