Правильная реализация MVVM в Swift - PullRequest
0 голосов
/ 26 августа 2018

Я пытаюсь использовать MVVM в своем приложении.Я обнаружил, что MVVM реализуется в разных форматах.Так что это сбивает с толку, какой метод я должен использовать.У меня есть две разные реализации для случая извлечения пользователей и отображения списка в табличном представлении.

Модель определяется как:

struct User: Decodable {
    var id: String?
    var name: String?
    var email: String?
}

Для выборки по сети у меня есть следующий класс.

class ServiceManager {

    static let sharedInstance = ServiceManager()

    func fetchUsers(completion: @escaping ([User]?, Error?) -> ()) {

        Alamofire.request(
            URL(string: UrlManager.getUsers)!,
            method: .get,
            encoding: JSONEncoding.default
            ).responseJSON { (response) -> Void in

                guard let responseCode = response.response?.statusCode else {
                    return
                }

                print(responseCode)

                switch responseCode {

                case 200:
                    do {
                        let users = try JSONDecoder().decode([User].self, from: response.data!)
                        completion(users, nil)
                    } catch let jsonErr {
                        print("ERR=", jsonErr.localizedDescription, "-->", jsonErr)
                        completion(nil, jsonErr)
                    }

                default:
                    completion(nil, "error" as? Error)

                }
            }
        }
    }

ПОДХОД 1 выглядит следующим образом.

class ViewController: UIViewController {

    var userviewmodels = [UserViewModel]()

    private var selectedIndex: Int!

    @IBOutlet weak var tableView: UITableView!


    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

        fetchUserData()
    }

    func fetchUserData() {
        ServiceManager.sharedInstance.fetchUsers { (users, err) in
            if let error = err {
                print("Failed to fetch: \(error)")
                return
            }
            self.userviewmodels = users?.map({return UserViewModel(user: $0)}) ?? []

            self.tableView.reloadData()
        }
    }

}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return userviewmodels.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UserTableViewCell

        let userViewModel = userviewmodels[indexPath.row]

        cell.userViewModel = userViewModel

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedIndex = indexPath.row
        performSegue(withIdentifier: "showUserInfo", sender: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showUserInfo" {
            let userInfoViewController = segue.destination as! UserInfoViewController
            userInfoViewController.userviewmodel = self.userviewmodels[selectedIndex]
        }
    }
}

Модель просмотра выглядит следующим образом:

class UserViewModel {

    var name: String!
    var email: String!

    init(user: User) {
        self.name = user.name
        self.email = user.email
    }

}

Ячейка табличного представления:

class UserTableViewCell: UITableViewCell {

    @IBOutlet weak var labelName: UILabel!

    @IBOutlet weak var labelEmail: UILabel!

    var userViewModel: UserViewModel! {
        didSet {
            labelName.text = userViewModel.name
            labelEmail.text = userViewModel.email
        }
    }
}

Далее, ПОДХОД 2 приведен ниже.

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    private var userviewmodel = UserViewModel()

    private var selectedIndex: Int!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

        userviewmodel.delegate = self
        userviewmodel.fetchUsers()
    }

}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return userviewmodel.numberOfUsers()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UserTableViewCell

        cell.labelName.text = userviewmodel.getUserName(at: indexPath.row)
        cell.labelEmail.text = userviewmodel.getUserEmail(at: indexPath.row)

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedIndex = indexPath.row
        performSegue(withIdentifier: "showUserInfo", sender: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showUserInfo" {
            let userInfoViewController = segue.destination as! UserInfoViewController
            userInfoViewController.userviewmodel = userviewmodel
            userInfoViewController.index = selectedIndex
        }
    }
}

extension ViewController: UserViewModelDelegate {
    func usersFetched () {
        tableView.reloadData()
    }
}

Соответствующая модель вида:

protocol UserViewModelDelegate: class {
    func usersFetched()
}

class UserViewModel {

    weak var delegate: UserViewModelDelegate?

    private var users = [User]() {
        didSet {
            delegate?.usersFetched()
        }
    }
} 

extension UserViewModel {

    func fetchUsers() {

        ServiceManager.sharedInstance.fetchUsers { (users, err) in
            if let error = err {
                print("Failed to fetch: \(error)")
                return
            }

            self.users = users!
        }
    }
}

extension UserViewModel {

    func numberOfUsers() -> Int {
        return users.count
    }

    func getUserName(at index: Int) -> String {
        return users[index].name!
    }

    func getUserEmail(at index: Int) -> String {
        return users[index].email!
    }
}

Насколько я понимаю,ViewModel должен уведомить ViewController о данных, которые будут представлены.Так значит ли это, что сетевые вызовы должны быть помещены в ViewModel (как подход 2)?

И, предположим, мне нужно изменить информацию о пользователе в другом ViewController (переходы запускаются в приведенных выше примерах).В этом случае допустима ли передача объекта ViewModel другому ViewController, особенно для подхода 2?

Я читал, что для MVVM лучше использовать RxSwift, RxCocoa, но я все еще изучаю их.Так какой метод я должен использовать в настоящее время: подход 1, подход 2 или что-то еще?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...