Sobes.tech
Back to questions
Senior
128
questionbank

Как вы переводили сущности с архитектуры NVC на Clean Swift?

Sobes Copilot

Get answers in real time

Answer from AI

sobes.tech AI

Переход от MVC к Clean Swift ( VIP) включает перераспределение ответственности из ViewController в Worker, Interactor, Presenter и Router.

Основные этапы:

  1. Идентификация сущностей и потоков: Определить, какие ViewController соответствуют каждому VIP-модулю и какие взаимодействия происходят.
  2. Создание шаблонов Clean Swift компонентов: Сгенерировать или создать вручную шаблоны для Interactor, Presenter, Worker, Router, Model и ViewController для каждого переводимого модуля.
  3. Рефакторинг ViewController: Удалить бизнес-логику и логику представления, оставляя только UI-код и код, взаимодействующий с Interactor и Router.
    • Обработчики событий UI отправляют запросы в Interactor.
    • ViewController обновляется на основе данных, полученных от Presenter.
  4. Перенос бизнес-логики в Interactor: Бизнес-правила, взаимодействие с Worker'ами и обработка входящих запросов переносятся в Interactor. Interactor вызывает Worker'ы для выполнения конкретных задач (сетевые запросы, работа с базой данных и т.д.).
  5. Перенос логики представления в Presenter: Преобразование данных из Interactor в формат, пригодный для отображения на UI. Presenter отправляет готовые данные во ViewController.
  6. Создание Worker'ов: Реализация конкретных операций (например, загрузка данных с сервера) в отдельных Worker-классах. Worker'ы вызываются из Interactor.
  7. Настройка Router: Определение переходов между модулями. Router отвечает за создание и настройку следующего VIP-модуля и передачу данных при переходе.
  8. Настройка связей: Установить связи между компонентами каждого VIP-модуля (например, ViewController содержит ссылку на Interactor, Interactor на Presenter и Worker'ы, Presenter на ViewController, Router содержит ссылки на Data Passing протоколы).
  9. Тестирование: Написание модульных и интеграционных тестов для каждого компонента (Interactor, Presenter, Worker) и проверка корректности работы всей системы.

Пример переноса логики загрузки данных:

В MVC:

// ViewController.swift
class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        loadData()
    }

    func loadData() {
        // Сетевой запрос
        NetworkService.shared.fetchData { [weak self] result in
            switch result {
            case .success(let data):
                // Обработка данных
                self?.displayData(data)
            case .failure(let error):
                // Обработка ошибки
                self?.displayError(error)
            }
        }
    }

    func displayData(_ data: Data) {
        // Обновление UI
    }

    func displayError(_ error: Error) {
        // Отображение ошибки на UI
    }
}

// NetworkService.swift
class NetworkService {
    static let shared = NetworkService()

    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        // Реализация сетевого запроса
    }
}

В Clean Swift:

// MyViewControlller.swift (остается только UI и вызовы 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) {
        // Обновление UI на основе viewModel
    }

    func displayError(viewModel: My.FetchData.ViewModel) {
        // Отображение ошибки на UI на основе viewModel
    }
}

// MyInteractor.swift (бизнес-логика и взаимодействие с Worker)
protocol MyBusinessLogic {
    func fetchData(request: My.FetchData.Request)
}

protocol MyDataStore {
    // Какое-то состояние
}

class MyInteractor: MyBusinessLogic, MyDataStore {
    var presenter: MyPresentationLogic?
    var worker: MyWorker? // Worker для сетевых запросов

    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 (логика представления)
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) {
        // Преобразование данных для ViewController
        let viewModel = My.FetchData.ViewModel(displayData: "Processed: \(response.data!)", errorMessage: nil)
        viewController?.displayData(viewModel: viewModel)
    }

    func presentError(response: My.FetchData.Response) {
        // Преобразование ошибки для ViewController
        let viewModel = My.FetchData.ViewModel(displayData: nil, errorMessage: "Error: \(response.error!.localizedDescription)")
        viewController?.displayError(viewModel: viewModel)
    }
}

// MyWorker.swift (конкретная операция - сетевой запрос)
class MyWorker {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        // Реализация сетевого запроса (аналог NetworkService в MVC)
    }
}

// MyRouter.swift (логика навигации)
protocol MyRoutingLogic {
    func routeToNextScreen()
}

class MyRouter: MyRoutingLogic {
    weak var viewController: UIViewController?

    func routeToNextScreen() {
        // Реализация перехода
    }
}

// MyModels.swift (структуры и протоколы для данных между компонентами)
enum My {
    enum FetchData {
        struct Request {}
        struct Response {
            let data: Data?
            let error: Error?
        }
        struct ViewModel {
            let displayData: String?
            let errorMessage: String?
        }
    }
     // Другие сценарии использования
}

Процесс требует тщательного анализа существующего кода и пошагового переноса логики в соответствующие слои Clean Swift, обеспечивая тестирование на каждом этапе. Использование шаблонов и инструментов Clean Swift может ускорить этот процесс.