Sobes.tech
Middle+

Как да използваме архитектурата MVC, за да избегнем претоварване на контролера?

sobes.tech AI

Отговор от AI

MVC (Model-View-Controller) в iOS често води до "дебели" контролери поради разполагането на бизнес логика и логика на представяне в тях. За решаване на този проблем могат да се използват следните подходи:

  • Преместване на логиката на представяне в View: Създаване на персонализирани UIView или клетки на таблица/колекция, които отговарят за показването на данни и обработката на основни действия на потребителя (например натискане на бутони вътре в клетката). Контролерът само конфигурира View с необходимите данни.

    class MyCustomView: UIView {
        private let label = UILabel()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            addSubview(label)
            // Настройка на constraints и т.н.
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        func configure(with text: String) {
            label.text = text
        }
    
        // Обработка на събития UIControlEvents, например чрез addTarget
    }
    
  • Преместване на бизнес логиката в Model или отделни Service обекти: Създаване на класове, отговорни за конкретни операции (например работа с мрежа, база данни, валидиране на данни). Контролерът делегира изпълнението на тези задачи на тези обекти.

    class UserService {
        func fetchUsers(completion: @escaping ([User]?, Error?) -> Void) {
            // Логика за мрежовата заявка
        }
    }
    
    class ProfileViewController: UIViewController {
        private let userService = UserService()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            userService.fetchUsers { users, error in
                // Актуализиране на UI въз основа на получените данни или грешка
            }
        }
    }
    
  • Използване на протоколи Data Source и Delegate: Реализиране на UITableViewDataSource, UICollectionViewDataSource, UITableViewDelegate, UICollectionViewDelegate в отделни класове, а не в самия контролер. Контролерът ще притежава само екземпляри от тези класове.

    class UsersTableViewDataSource: NSObject, UITableViewDataSource {
        private var users: [User] = []
    
        func update(users: [User]) {
            self.users = users
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return users.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
            let user = users[indexPath.row]
            cell.textLabel?.text = user.name
            return cell
        }
    }
    
    class UsersViewController: UIViewController {
        @IBOutlet weak var tableView: UITableView!
        private let dataSource = UsersTableViewDataSource()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = dataSource
            // Зареждане на данни и предаване към dataSource.update(users:)
        }
    }
    
  • Прилагане на Pattern на координатора: Създаване на отделни обекти, отговорни за навигацията между екрани. Контролерите знаят само, че трябва да преминат към следващата стъпка, но не и кой контролер ще бъде следващият.

    protocol AppCoordinator: AnyObject {
        func start()
        func showUserDetails(user: User)
    }
    
    class MainAppCoordinator: AppCoordinator {
        private let navigationController: UINavigationController
    
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
    
        func start() {
            let usersVC = UsersViewController()
            // Настройка на делегат, dataSource и т.н.
            navigationController.pushViewController(usersVC, animated: false)
        }
    
        func showUserDetails(user: User) {
            let userDetailsVC = UserDetailsViewController(user: user)
            navigationController.pushViewController(userDetailsVC, animated: true)
        }
    }
    

Тези подходи помагат за облекчаване на натоварването на контролера, като го правят по-"тънък" и се фокусират върху управлението на жизнения цикъл на View и координацията на взаимодействието между Model и View, а не върху изпълнението на цялата логика на приложението.