Давайте посмотрим документацию о .receive(on:)
Указывает планировщик для получения элементов от издателя. Декларация
func receive<S>(on scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.ReceiveOn<Publishers.SubscribeOn<Deferred<Future<[Model], FileError>>, DispatchQueue>, S> where S : Scheduler
Обсуждение
Оператор receive(on:options:)
используется для получения результатов в определенном планировщике c, например при выполнении пользовательского интерфейса в основном прогоне l oop. В отличие от subscribe(on:options:)
, который влияет на восходящие сообщения, receive(on:options:)
изменяет контекст выполнения нижестоящих сообщений. В следующем примере запросы к jsonPublisher выполняются в backgroundQueue, но полученные от него элементы выполняются в RunL oop .main.
let jsonPublisher = MyJSONLoaderPublisher() // Some publisher.
let labelUpdater = MyLabelUpdateSubscriber() // Some subscriber that updates the UI.
jsonPublisher
.subscribe(on: backgroundQueue)
.receiveOn(on: RunLoop.main)
.subscribe(labelUpdater)
Параметры
планировщик
Планировщик издатель должен использовать для доставки элементов. Параметры планировщика, которые настраивают доставку элемента. Возвращает
Издатель, который доставляет элементы с использованием указанного планировщика.
в вашем случае это означает
import SwiftUI
import Combine
enum FileError: Error {
case someError
}
class ViewModel: ObservableObject {
@Published var modelArray = [Model]()
private var subscriptions = Set<AnyCancellable>()
func readData() {
DataSource()
.readFromBundle(resource: "Sample", type: "json")
.sink(receiveCompletion: { completion in
print("Completion: \(completion)")
}) { array in
print("received value")
self.modelArray = array
}.store(in: &subscriptions)
}
}
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
List(self.viewModel.modelArray) { model in
Text("\(model.name)")
}
}
.onAppear {
self.viewModel.readData()
}
}
}
struct Model: Codable, Identifiable {
var id: Int
var name: String
}
class DataSource {
private let readQueue = DispatchQueue(label: "ReadQueue", qos: .default, attributes: .concurrent)
func readFromBundle (resource: String, type:String) -> AnyPublisher<[Model], FileError> {
Deferred {
Future { promise in
guard let url = Bundle.main.url(forResource: "Sample", withExtension: "json"),
let data = try? Data(contentsOf: url),
let modelArray = try? JSONDecoder().decode([Model].self, from: data)
else {
promise(.failure(.someError))
return
}
promise(.success(modelArray))
}
}
.subscribe(on: readQueue)
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
, который объясняет, почему работает решение Asperi. Разница в том, что нет необходимости снова вызывать .receive (on :) в readData()
разница между DispatchQueue.main
и RunLoop.main
в вашем примере незначительна.