Combine with UITableView

Hello,

I'm trying to work out a simple example to fill table view data with Combine. The following is what I have.

import Foundation

struct MyModel: Decodable {
	let id: String
	let type: String
}
import UIKit
import Combine

class APIClient: NSObject {
	var cancellable: AnyCancellable?
	let sharedSession = URLSession.shared
		
	func fetchData(urlStr: String, completion: @escaping ([MyModel]?) -> Void) {
		guard let url = URL(string: urlStr) else {
			return
		}
		let publisher = sharedSession.dataTaskPublisher(for: url)
		cancellable = publisher.sink(receiveCompletion: { (completion) in
			switch completion {
			case .failure(let error):
				print(error)
			case .finished:
				print("Success")
			}
		}, receiveValue: { (result) in
			let decoder = JSONDecoder()
			do {
				let post = try decoder.decode([MyModel].self, from: result.data)
				completion(post)
			} catch let error as NSError {
				print("\(error)")
				completion(nil)
			}
		})
	}
}
import Foundation

class ViewModel: NSObject {
	@IBOutlet var apiClient: APIClient!
	var dataModels = [MyModel]()
	
	func getGitData(completion: @escaping () -> Void) {
		let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events"
		apiClient.fetchData(urlStr: urlStr) { (models) in
			if let myModels = models {
				self.dataModels = myModels.map { $0 }
			}
			completion()
		}
	}
}
import UIKit
import Combine

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
	// MARK: - Variables
	var cancellable: AnyCancellable?
	@IBOutlet var viewModel: ViewModel!
	@Published var models = [MyModel]()
	
	
	// MARK: - IBOutlet
	@IBOutlet weak var tableView: UITableView!
	
	
	// MARK: - Life cycle
	override func viewDidLoad() {
		super.viewDidLoad()
		
		viewModel.getGitData {
			self.models = self.viewModel.dataModels
		}
		
		cancellable = $models.sink(receiveValue: { (result) in
			DispatchQueue.main.async {
				[weak self] in
				guard let strongSelf = self else { return }
				strongSelf.tableView.reloadData()
			}
		})
	}
	
	
	// MARK: - TableView
	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return models.count
	}
	
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
		let dataModel = models[indexPath.row]
		cell?.textLabel?.text = dataModel.id
		cell?.detailTextLabel?.text = dataModel.type
		return cell!
	}
}

I'm not quite comfortable with the lines of code under my view controller (ViewController) in using Combine. How can I make them better? Muchos thankos.

What do you think is better?

I don't know if my approach in reading view model data is correct. At least, I don't think it's efficient in the view controller.

I guess in your case I would bind directly the response from your API to the model, so u can react to it.

In your API you could send the publisher directly, and drive it across the view model to the actual view.

import Combine

class APIClient: NSObject {
	var cancellable: AnyCancellable?
	let sharedSession = URLSession.shared
		
	func fetchData(urlStr: String) -> AnyPublisher<[MyModel], Error> {
		guard let url = URL(string: urlStr) else {
			return
		}
		let publisher = sharedSession.dataTaskPublisher(for: url)
		cancellable = publisher
        .tryMap { [unowned self] result in
           let decoder = JSONDecoder()
			     return try decoder.decode([MyModel].self, from: result.data)
        }
        .eraseToAnyPublisher()
	 }
}
import Foundation

class ViewModel: NSObject {
	@IBOutlet var apiClient: APIClient!
	
	func getGitData() -> AnyPublisher<[MyModel], Error>  {
		let urlStr = "https://api.github.com/repos/ReactiveX/RxSwift/events"
		return apiClient.fetchData(urlStr: urlStr)
	}
}
import UIKit
import Combine

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
	// MARK: - Variables
	var cancellables: [AnyCancellable]
	var viewModel: ViewModel!
  @Published var models = [MyModel]()
	
	// MARK: - IBOutlet
	@IBOutlet weak var tableView: UITableView!
	
	// MARK: - Life cycle
	override func viewDidLoad() {
		super.viewDidLoad()
		
		viewModel.getGitData()
     .sink { completion in
        guard case .failure(let error) = completion else { return }
        print(error)
     } receiveValue: { (result) in
		    DispatchQueue.main.async { [weak self] models in
		      guard let strongSelf = self else { return }
          strongSelf.models = models
        }
     }
    .store(on: $cancellables)

   $models()
     .sink { _ in
        self.tableView.reloadData()
     }
    .store(on: $cancellables)
	}
	
	// MARK: - TableView
	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return models.count
	}
	
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
		let dataModel = models[indexPath.row]
		cell?.textLabel?.text = dataModel.id
		cell?.detailTextLabel?.text = dataModel.type
		return cell!
	}
}
Combine with UITableView
 
 
Q