多次使用 onReceive 会导致重复 API 调用

Multiple onReceive usages lead to duplicated API calls

我们开始在我们的应用程序中使用 Combine for Networks 调用。用户应该输入代码,我们希望通过网络调用验证该代码,然后根据所述网络调用的结果执行操作。

根据我们的理解,如果我们有一个已发布的字符串变量并使用它来懒惰地创建另一个发布者,我们在其中进行了 API 调用,那么每次我们只会进行 API 调用字符串已更改。

但是,通过 onReceive 监听发布者的 SwiftUI 视图越多,进行的 API 调用就越多。这是一个简化的例子:

查看

struct ContentView: View {

    @ObservedObject var viewModel: ResultBenchmarkViewModel

    @State var one: String = "ONE"
    @State var two: String = "TWO"
    @State var three: String = "THREE"

    var body: some View {
        VStack {
        
            TextField("INPUT", text: $viewModel.inputString)
        
            Text(one)
                .onReceive(viewModel.inputStringPublisher, perform: { string in
                    one = string
                })
        
            Text(two)
                .onReceive(viewModel.inputStringPublisher, perform: { string in
                    two = string
                })
        
            Text(three)
                .onReceive(viewModel.inputStringPublisher, perform: { string in
                    three = string
                })
        }
    }
}

ViewModel

class ResultBenchmarkViewModel: Identifiable, ObservableObject {

    @Published var inputString: String = ""

    lazy var inputStringPublisher: AnyPublisher<String, Never> = {
    
        return $inputString
            .map { string in
                print("API CALL")
                return string
            }
            .eraseToAnyPublisher()
    }()
}
每次用户输入一个字符时,

使用三次 onReceive 也会打印三次“API CALL”。我们预期会发生的是“API CALL”只会打印一次,因为 inputString 只会更改一次,然后使用 onReceive 的三个视图只会收到发布的最终结果由出版商提供。

我们现在正在实施的解决方案是监听 inputString,进行 api 调用并将结果保存到本地发布的变量,然后视图根据该变量进行更新。然而,这对我来说似乎是样板文件,如果我们正确地实现了最初的要求,它可能已经过时了。

我们是否可以通过对实施进行一些小的改动来达到预期的结果?还是这里对combine/swiftui存在根本性的误解?

您可以'share' 发布者的输出。这就是 Share Publisher 的用途。

https://developer.apple.com/documentation/combine/fail/share()

使用 Share Publisher,您的 inputStringPublisher 将如下所示:

lazy var inputStringPublisher: AnyPublisher<String, Never> = {

    return $inputString
        .map { string in
            print("API CALL")
            return string
        }
        .share()
        .eraseToAnyPublisher()
}()

就个人而言,我会继续使用已发布的变量解决方案,因为视图模型中有更多逻辑。

您需要将 .share 修饰符添加到您的 inputStringPublisher:

return $inputString
  .map { string in
    print("API CALL")
    return string
  }
  .share()
  .eraseToAnyPublisher()