在 ObservableObject 中调用多个函数

Posted

技术标签:

【中文标题】在 ObservableObject 中调用多个函数【英文标题】:Calling multiple functions in ObservableObject 【发布时间】:2020-12-24 06:24:43 【问题描述】:

我之前问过一个问题,但我认为我不清楚自己要做什么。我创建了一个必须调用两个函数的ObservableObject 类。我需要首先为新 URL 调用 load,然后将该 URL 设置为 loadImage,但不确定如何正确调用它以在每个按钮操作中获取新图像。一个用户添加了一个函数不知道另一个是否完成的评论,我该如何解决这个问题?

目前它确实会在每次按下按钮时获取一个新的image,但我必须添加这个,但代码在我的内容视图中似乎不正确。

func load() 
        info.load()
        info.loadImage(from: URL(string:info.cats.file)!)
    

我的ObservableObject 课程在下面。

class CatInfo : ObservableObject 
    
    @Published var cats: RandomCats = RandomCats(file: "https://purr.objects-us-east-1.dream.io/i/kef4p.jpg")
    @Published var image: UIImage = UIImage(systemName:"exclamationmark")!
    
    var cancellables: Set<AnyCancellable> = Set<AnyCancellable>()
    var loaded: Bool = false
    
    init(cats: RandomCats) 
        self.cats = cats
    
    
    init() 
        load()
        loadImage(from: URL(string: self.cats.file)!)
    

    func load() 
        URLSession.shared
            .dataTaskPublisher(for: URLRequest(url: URL(string: "http://aws.random.cat/meow")!))
            .map(\.data)
            .decode(type: RandomCats.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion:  completion in
                switch completion 
                case .finished:
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                
            , receiveValue:  data in
                self.cats = data
                print(self.cats.file)
            )
            .store(in: &self.cancellables)
    

    
    func loadImage(from url: URL) 
        URLSession.shared
            .dataTaskPublisher(for: URLRequest(url: url))
            .map(\.data)
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion:  completion in
                switch completion 
                case .finished:
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                
            , receiveValue:  data in
                if  let image = UIImage(data: data) 
                    self.image = image
                    self.loaded = true
                
            )
            .store(in: &self.cancellables)
    


在我的内容视图中,我为按钮的操作创建了另一个调用 func load()func loadImage(from url: URL) 的加载函数。

struct ContentView: View 
    @ObservedObject var info: CatInfo
    public var defaultImage: UIImage = UIImage(systemName:"exclamationmark")!

    var body: some View 
        VStack 
            if info.loaded 
                Image(uiImage: (info.image))
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 300, height:500)
             else 
                Image(systemName:"exclamationmark")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 100, height:100)
                
            
            Button(action: load, label: 
                Text("Randomize")
                    .font(.title2)
            )
        
        .padding(.all)
    

    func load() 
        info.load()
        info.loadImage(from: URL(string:info.cats.file)!)
    

【问题讨论】:

我不清楚你想要完成什么。 load 函数和 loadImage 函数是如何相互关联的?您是否要从现有(即已检索)的 URL 加载图像,然后使用 load 更新 URL?还是相反:load 首先是新 URL,然后使用该 URL 到 loadImage?正如现在编码的那样,这两个函数是相互独立编码的,同时是异步的。这意味着一个功能不知道另一个是否完成 我建议你重写这个问题:你想要实现什么行为,你观察到什么(即错误是什么),你在load调用中得到了什么(我们现在只能猜测),并删除与问题无关的任何内容(例如所有#if os 的东西、样式等...) 我正在尝试相反的 load 新 URL,然后将该 URL 设置为 loadImage。这正是我正在寻找的,抱歉措辞不正确。会更新的。 我理解你的问题,使用 KingFisher 很容易。但是我们也可以通过这种方式做到这一点。 @colecabral,看起来您找到了一种方法,尽管您将组合和回调混合在一起有点奇怪。一般来说,Combine 管道可以链接到异步调用序列,因此您可以创建一个既加载数据又加载图像的管道。 【参考方案1】:

由于您的图片依赖于您从互联网上获取的随机 url,因此您必须在获取图片 url 后调用图片加载函数。

1.删除 ContentView 中的 load() 并仅在按钮操作中调用 info.load()

2.调用 func loadImage(from URL(string:info.cats.file)!) 后 url 在load() 中接收值。

注意:您可以使用 KingFisherSwiftUi 轻松完成此操作,使用 KingFisher 您只需提供图片视图的 url,无需手动加载图片。

【讨论】:

最好不要使用任何第三方工具。我基本上还在学习基础知识。我想我设法解决了我的问题。 我的回答对你有帮助吗? 不幸的是它没有帮助。不过还是谢谢【参考方案2】:

我对您的问题的理解是,您首先需要获取图片 URL,然后使用该 URL 来实际加载图片。

要使用两个异步函数来实现这一点,您不能一个接一个地同步调用它们。相反,第二个(依赖)函数只能在第一个函数完成时调用,这可能会在以后的某个未知时间发生。

通常,此用例通过回调(类似于您在已删除答案中所做的)或链接组合运算符来解决。

例如,如果你有这些功能:

func loadImageURL() -> AnyPublisher<URL, Error> ...
func loadImage(for url: URL) -> AnyPublisher<UIImage, Error> ...

然后您可以将它们链接起来以产生最终结果:

let cancellable = loadImageURL()
   .flatMap  url in
      loadImage(for: url)
   
   .catch  _ in Empty()  // ignore errors for simplicity
   .sink  image in
      self.image = image
   

因此,您可以创建一个 func randomize() .. 来执行此操作,但它会不断生成新的订阅。或者,您可以使用 PassthroughSubject 并设置一个不断更新 image 属性的订阅:

class CatInfo: ObservableObject 
   @Published var image: UIImage? = nil
   private let randomizeSubject = PassthroughSubject<Void, Never>()
   private var cancellables: Set<AnyCancellable> = []

   init() 
      randomizeSubject
        .flatMap 
            loadImageURL().catch  _ in Empty() 
        
        .flatMap  url in
            loadImage(for: url).catch  _ in Empty() 
        
        .receive(on: DispatchQueue.main)
        .sink  [weak self] image in
            self?.image = image
        
        .store(in: &cancellables)
   

   func randomize() 
      randomizeSubject.send(())
   

【讨论】:

谢谢你的详细解释,我试试看!我设法让它在没有组合的情况下工作,但我会看看我是否可以将它整合到我现在拥有的东西中。

以上是关于在 ObservableObject 中调用多个函数的主要内容,如果未能解决你的问题,请参考以下文章

如何告诉 SwiftUI 视图绑定到多个嵌套的 ObservableObject

如何从我的 ObservableObject 类调用我的 View 结构中的方法

SwiftUI/组合监听 ObservableObject 中的多个发布者

调用 ObservableObject 类初始化器来更新 SwiftUI 中的 List

SwiftUI ObservableObject 的行为不一致,具体取决于调用站点的来源

ObservableObject 不更新视图