Senior
How did you translate entities from the NVC architecture to Clean Swift?
sobes.tech AI
Answer from AI
Transitioning from MVC to Clean Swift (VIP) involves redistributing responsibilities from ViewController to Worker, Interactor, Presenter, and Router.
Main steps:
- Identify entities and flows: Determine which ViewControllers correspond to each VIP module and what interactions occur.
- Create templates for Clean Swift components: Generate or manually create templates for Interactor, Presenter, Worker, Router, Model, and ViewController for each module being migrated.
- Refactor ViewController: Remove business logic and presentation logic, leaving only UI code and code interacting with Interactor and Router.
- Event handlers send requests to Interactor.
- ViewController updates based on data received from Presenter.
- Move business logic to Interactor: Business rules, interactions with Workers, and request handling are moved to Interactor. Interactor calls Workers to perform specific tasks (network requests, database operations, etc.).
- Transfer presentation logic to Presenter: Transform data from Interactor into a format suitable for UI display. Presenter sends prepared data to ViewController.
- Create Workers: Implement specific operations (e.g., loading data from server) in separate Worker classes. Workers are called from Interactor.
- Configure Router: Define transitions between modules. Router is responsible for creating and setting up the next VIP module and passing data during navigation.
- Set up connections: Establish links between components of each VIP module (e.g., ViewController has a reference to Interactor, Interactor to Presenter and Workers, Presenter to ViewController, Router contains references to Data Passing protocols).
- Testing: Write unit and integration tests for each component (Interactor, Presenter, Worker) and verify the system's correctness.
Example of loading data logic transfer:
In MVC:
// ViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
// Network request
NetworkService.shared.fetchData { [weak self] result in
switch result {
case .success(let data):
// Handle data
self?.displayData(data)
case .failure(let error):
// Handle error
self?.displayError(error)
}
}
}
func displayData(_ data: Data) {
// Update UI
}
func displayError(_ error: Error) {
// Show error on UI
}
}
// NetworkService.swift
class NetworkService {
static let shared = NetworkService()
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// Implementation of network request
}
}
In Clean Swift:
// MyViewController.swift (remains only UI and calls to Interactor)
protocol MyDisplayLogic: AnyObject {
func displayData(viewModel: My.FetchData.ViewModel)
func displayError(viewModel: My.FetchData.ViewModel)
}
class MyViewController: UIViewController, MyDisplayLogic {
var interactor: MyBusinessLogic?
var router: MyRoutingLogic?
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
let request = My.FetchData.Request()
interactor?.fetchData(request: request)
}
func displayData(viewModel: My.FetchData.ViewModel) {
// Update UI based on viewModel
}
func displayError(viewModel: My.FetchData.ViewModel) {
// Show error on UI based on viewModel
}
}
// MyInteractor.swift (business logic and interaction with Worker)
protocol MyBusinessLogic {
func fetchData(request: My.FetchData.Request)
}
protocol MyDataStore {
// Some state
}
class MyInteractor: MyBusinessLogic, MyDataStore {
var presenter: MyPresentationLogic?
var worker: MyWorker? // Worker for network requests
func fetchData(request: My.FetchData.Request) {
worker?.fetchData { [weak self] result in
switch result {
case .success(let data):
let response = My.FetchData.Response(data: data, error: nil)
self?.presenter?.presentData(response: response)
case .failure(let error):
let response = My.FetchData.Response(data: nil, error: error)
self?.presenter?.presentError(response: response)
}
}
}
}
// MyPresenter.swift (presentation logic)
protocol MyPresentationLogic {
func presentData(response: My.FetchData.Response)
func presentError(response: My.FetchData.Response)
}
class MyPresenter: MyPresentationLogic {
weak var viewController: MyDisplayLogic?
func presentData(response: My.FetchData.Response) {
// Transform data for ViewController
let viewModel = My.FetchData.ViewModel(displayData: "Processed: \(response.data!)", errorMessage: nil)
viewController?.displayData(viewModel: viewModel)
}
func presentError(response: My.FetchData.Response) {
// Transform error for ViewController
let viewModel = My.FetchData.ViewModel(displayData: nil, errorMessage: "Error: \(response.error!.localizedDescription)")
viewController?.displayError(viewModel: viewModel)
}
}
// MyWorker.swift (specific operation - network request)
class MyWorker {
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// Implementation of network request (similar to NetworkService in MVC)
}
}
// MyRouter.swift (navigation logic)
protocol MyRoutingLogic {
func routeToNextScreen()
}
class MyRouter: MyRoutingLogic {
weak var viewController: UIViewController?
func routeToNextScreen() {
// Implementation of transition
}
}
// MyModels.swift (data structures and protocols for component communication)
enum My {
enum FetchData {
struct Request {}
struct Response {
let data: Data?
let error: Error?
}
struct ViewModel {
let displayData: String?
let errorMessage: String?
}
}
// Other use cases
}
The process requires careful analysis of existing code and step-by-step transfer of logic into the appropriate Clean Swift layers, ensuring testing at each stage. Using templates and Clean Swift tools can accelerate this process.