Я пытаюсь использовать 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 или что-то еще?