メモ程度なので雑に書いてます。
Firestore+Combine.swift
import Foundation import Combine import FirebaseFirestore extension Firestore { func fetchAll<T: EntityProtocol>(collectionKey: String) -> AnyPublisher<[T], Error> { return Future<[T], Error> { [weak self] promise in self?.collection(collectionKey).getDocuments() { snapshot, error in if let err = error { promise(.failure(err)) } if let snapshot = snapshot { let entities: [T] = snapshot.documents.compactMap { T.decode(document: $0.data()) } promise(.success(entities)) } } }.eraseToAnyPublisher() } }
Repository.swift
import Foundation import Combine import FirebaseFirestore protocol RepositoryProtocol { func fetchAll() -> AnyPublisher<[Entity], Error> } final class Repository: RepositoryProtocol { func fetchAll() -> AnyPublisher<[Entity], Error> { return Firestore.firestore().fetchAll(collectionKey: "sample") } }
※EntityProtocolにはdecodeとdocument変換([String: Any])を定義しています。
ViewModel.swift
import Foundation import SwiftUI import Combine final class ViewModel: ObservableObject { @Published private(set) var entities: [Entity] = [] var cancellables: Set<AnyCancellable> = [] init(with repository: RepositoryProtocol = Repository()) { repository.fetchAll() .receive(on: DispatchQueue.main) .sink( receiveCompletion: { _ in }, receiveValue: { [weak self] entities in self?.entities = entities }).store(in: &cancellables) } deinit { cancellables.forEach { $0.cancel() } } }
ContentView.swift
import SwiftUI struct ContentView: View { @ObservedObject var viewModel: ViewModel var body: some View { NavigationView { ScrollView { ForEach(viewModel.subscriptions, id: \.id) { entity in HStack { VStack { Text(entity.title) .font(.system(size: 18, weight: .medium, design: .default)) Text(entity.description) .font(.system(size: 14, weight: .medium, design: .default)) } } } } .navigationTitle("Content View") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: .init()) } }