Contents
Chào bạn đến với seri Lập trinh iOS cho mọi người. Chủ đề của bài viết này sẽ khá là dài lẫn nội dung và lượng lý thuyết truyền tải. Để chuẩn bị vào bài thì bạn phải cần nắm được một số kiến thức sau:
Bài viết sẽ đề cập tới một mô hình được sử dụng khá nhiều trong giới lập trình iOS. Đó là MVVM (Model – View – ViewModel). Đây là mô hình tiến hoá của MVC truyền thống. Nên nếu bạn chưa hiểu hay chưa biết về MVC thì tạm thời quay về bài viết MVC để bổ túc kiến thức rồi hãy quay lại với MVVM.
Bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. MVVM là gì?
Lập trình cũng giống như cuộc sống. Và con người vẫn luôn tìm tòi con đường để hướng bản thân mình tới được Chân – Thiện – Mĩ. Thì lập trình cũng giống vậy. Trải qua bao sóng gió trong lịch sử, lập trình đã tiến hoá lên rất nhiều. Các thế hệ sau luôn tìm ra cách để cải thiện những nhược điểm của người tiền nhiệm.
Trong số đó, Mô hình là cái được nhiều người quan tâm nhất. Mô hình hay còn được gọi là Design Pattern (có thể hơi sai nha). Nó giống như kinh thánh trong giới lập trình vậy. Ngăn bản thân lập trình viên không làm những điều sai trái. Đem lại một chân lý, một lý tưởng sống cho từng cá nhân hay cả team… Và mục đích chính thì nó giúp mọi người hướng tới:
Chân – Thiện – Mĩ trong code.
1.1. Bối cảnh lịch sử
Sự ra đời của một thứ gì đó, thì phải có nguyên nhân và bối cảnh riêng. Trong giới lập trình khi còn sơ khai, thì MVC như là vị cứu tinh sống còn. Nếu bạn đã từng làm việc với MVC, thì gánh nặng cuộc đời của bạn đều thả vào Controller. Bao nhiêu đắng cay cuộc đời nó đều cam chịu và nhận lấy, ví dụ như:
- Gởi dữ liệu cho View để hiển thị
- Xử lí dữ liệu
- Nhận sự kiện của người dùng từ View
- Tương tác với Model
- Quản lý các đối tượng
- Xử lí đa luồng
Cũng như Window vậy, lúc mới thì chạy rất nhanh. Qua thời gian, thì số lượng mã nguồn nhiều thêm, các function dày thêm. Bạn sẽ thấy áp lực mà Controller gánh chịu rất lớn. Một trong những cái kinh khủng nhất đó là.
Sự đồng bộ về các kĩ thuật thao tác được sử dụng trong mã nguồn.
Ví dụ: như ban đầu bạn xử lí Custom View để trả về dữ liệu bằng protocol. Nhưng sau đó bạn lại dùng closure để thay thế.
Nhưng đó chỉ là gánh nặng mà thôi, nếu trình độ dev bạn cao thâm, thì không có gì phải sợ. Nhiều nhưng không phải là rối. Và việc gì tới thì có cũng sẽ tới. Quan trọng nhất chính là:
Testing
Tại sao là Testing?
- Đảm bảo chất lượng dự án
- Đảm bảo chất lượng code khi refactor mã nguồn hay update thêm tính năng.
Và với lập trình viên thì bạn có 2 kĩ thuật test cơ bản:
- Unit test
- UI test
Cả hai cái này đều khó khăn hoặc không thể dùng với MVC. Nhất là khi lượng code quá nhiều và các đối tượng liên kết với nhau quá kĩ. Giải quyết vấn đề này thì phải
Phá vỡ các liên kết giữa các đối tượng trong mô hình. Vì vậy, MVVM được khai sinh.
1.2. Mô hình MVVM
Về định nghĩa MVVM là gì? Bạn chỉ cần vài đường Google cơ bản là ra liền. Mình sẽ hạn chế phần này, để tập trung vào cách xử lí & tương tác giữa các thành phần trong MVVM.
Trước khi có MVVM, thì thế giới lập trình cũng đưa ra nhiều biến thế cho cho MVC, như: MVP, MV* (gì cũng được)… để giảm áp lực cho Controller. Tới MVVM thì nó giải quyết được cả 2 yêu cầu trên của bối cảnh lịch sử:
- Testing
- Giảm vai trò của Controller
Để testing được thì các mối quan hệ của các thành phần phải được tách ra.
- Controller vai trò giảm đi và nó được xem như là View
- View vẫn là chính nó
- Model vẫn là Model
- Khai sinh thêm đứa con của View + Model là ViewModel
ViewModel là điểm quan trọng nhất của cả mô hình. Nó chỉ là sự ánh xạ của View về mặt dữ liệu, nên bạn có thể Unit Test lên View bằng cách test ViewModel. Để test Model thì bạn có thể sử dụng dữ liệu giả (dummy data) và test các chức năng liên kết phức tạp thì thay phần đầu vào cung cấp cho ViewModel.
Các việc xử lý dữ liệu thì Controller đẩy sang cho ViewModel. Nên sang 1/2 gánh nặng từ Controller cho ViewModel.
Ví dụ:
Bạn có một UIViewController có 1 UILabel trong View, nhắm hiển thị ngày tháng năm sinh của 1 đối tượng User nào đó. Với
- MVC:
- Controller sẽ lấy dữ liệu lấy trong Database với kiểu dữ liệu là
Date
- Controller tiếp tục việc biến đổi Date ->
String
- Sau đó update dữ liệu mới lên trên UILabel
- Nếu người dùng có tương tác để thay đổi ngày tháng đó. Thì Controller sẽ làm các việc ngược lại
- Controller sẽ lấy dữ liệu lấy trong Database với kiểu dữ liệu là
- MVVM:
- ViewModel sẽ được tạo thêm, với ánh xạ những gì có trên View thì ViewModel sẽ có những cái đó.
- UILable sẽ tương đương với 1 property kiểu String
- Controller gọi ViewModel để lấy dữ liệu cho UILabel (tới đây controller xong nhiệm vụ) để hiển thị
- ViewModel gọi Model để lấy dữ liệu
Date
cho yêu cầu - ViewModel biến đổi Date thành String và trả lại cho Controller
- Nếu người dùng update ngày tháng, Controller sẽ nhận sự kiện và gọi hàm update của ViewModel.
- ViewModel sẽ được tạo thêm, với ánh xạ những gì có trên View thì ViewModel sẽ có những cái đó.
- Unit Test
- Nếu test trực tiếp từ Database thì rất nguy hiểm. Không tuỳ tiện edit mọi thứ của Database
- Phần dữ liệu test sẽ được nạp vào ViewModel (thay vì cho ViewModel lấy từ Database)
- Phần hiển thị và update thì vẫn giữ nguyên. Cả hệ thống vẫn hoạt động mượt mà.
Đó chỉ là 1 ví dụ đơn giản thôi, cho bạn hiểu sợ về MVVM. Còn sau đây là tóm tắt việc tiến hoá từ MVC sang MVVM qua hình sau:
1.3. Ưu & Nhược
1.3.1. Ưu
- Vì MVVM là mô hình nâng cấp của MVC, cho nên nó giúp app vẫn duy trì cấu trúc của mô hình MVC
- Giảm tải lượng code chứa trong View và View Controller.
- Khi đó View và View Controller trở nên đơn giản hơn khi những logic.
- Ví dụ như logic về quy định cách hiển thị của dữ liệu, được chuyển hết sang ViewModel. Điều này khiến cho code trở nên dễ hiểu và dễ maintain hơn.
- Sự liên lạc giữa các thành phần trong mô hình rõ ràng, khiến nó hoạt động tốt hơn với cơ chế binding dữ liệu.
- Có thể apply unit test lên lớp view model.
1.3.2. Nhược
- Nhiều file nên mã nguồn lại nhiều thêm
- Tương tác giữa các thành phần phức tạp
- Không giải quyết triệt để gánh nặng xử lí. Giờ đây, khổ chủ là ViewModel thay cho Controller
- Rắc rối trong việc phản hồi lại yêu cầu.
- …
2. Thành phần
Quá nhiều cho lý thuyết rồi.
Giờ sang phần thực thành. Và để mô tả lý thuyết, cộng với việc giải thích cách hoạt động thì mình sử dụng code
. Tạo 1 iOS Project không sử dụng Storyboard. Nếu bạn quên cách tạo thì tham khảo link dưới đây:
Và để tận dụng lại việc tạo project thì bạn có thể sử dụng Project của bài viết Tabbar Controller. Tuy nhiên, chúng ta sẽ cần tạo thêm phần cho Login View Controller.
Tiếp tục, mở file ScreenDelegate.swift
và tiến hành edit thêm một số đoạn code sau:
- Tạo enum cho loại ViewController cho root của Window
enum TypeScreen { case login case tabbar }
- Tạo function tạo Tabbar
private func createTabbar() { //Home let homeVC = HomeViewController() let homeNavi = UINavigationController(rootViewController: homeVC) homeNavi.tabBarItem = UITabBarItem(tabBarSystemItem: .featured, tag: 0) //Messages let messagesVC = MessagesViewController() let messagesNavi = UINavigationController(rootViewController: messagesVC) messagesNavi.tabBarItem = UITabBarItem(title: "Messages", image: UIImage(named: "ic-tabbar-messages"), selectedImage: UIImage(named: "ic-tabbar-messages-selected")) messagesNavi.tabBarItem.badgeValue = "99" messagesNavi.tabBarItem.badgeColor = .blue //Friends let friendsVC = FriendsViewController() let friendsNavi = UINavigationController(rootViewController: friendsVC) friendsNavi.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "ic-tabbar-friends"), tag: 2) //Profile let profileVC = ProfileViewController() let profileNavi = UINavigationController(rootViewController: profileVC) profileNavi.tabBarItem = UITabBarItem(title: "Profile", image: UIImage(named: "ic-tabbar-profile"), tag: 3) //tabbar controller let tabbarController = UITabBarController() tabbarController.delegate = self tabbarController.viewControllers = [homeNavi, messagesNavi, friendsNavi, profileNavi] tabbarController.tabBar.tintColor = .red window!.rootViewController = tabbarController }
- Tạo function tạo Login
private func createLogin() { let loginVC = LoginViewController() let loginNavi = BaseNavigationController(rootViewController: loginVC) window?.rootViewController = loginNavi }
- Tạo function thay đổi root
func changeScreen(type: TypeScreen) { switch type { case .login: createLogin() case .tabbar: createTabbar() } }
Thay đổi lại việc hiển thị ban đầu của project
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) self.window = window window.makeKeyAndVisible() changeScreen(type: .login) }
Bắt đầu code thôi!
2.1. View & Controller
Thành phần đầu tiên bạn cần phải biết trong MVVM là View
. Nó bao gồm cả:
- View
- Controller
Nhiệm vụ của nó như sau:
- Hiển thị dữ liệu lên giao diện
- Truyền sự kiện người dùng về cho ViewModel xử lí
- Lấy dữ liệu từ ViewModel và update lại giao diện
Mở file LoginViewController.swift
, có các function cơ bản phục vụ cho MVVM như sau:
class LoginViewController: BaseViewController { // MARK: - Properties @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - config override func setupUI() { super.setupUI() self.title = "Login" updateView() } override func setupData() { } func updateView() { } }
Giải thích:
- Các hàm cài đặt
setupUI
&setupData
. Chúng được cài đặt trong lớp chaBaseViewController
, và được gọi ởviewDidLoad
của lớp đó.- Khi lớp con là
LoginViewController
kế thừa lại và gọi lệnhsuper.viewDidLoad()
tạiviewDidLoad
của nó thì 2 hàm cài đặt kia sẽ thực thi
- Hàm cập nhật dữ liệu
updateUI
- Dùng để cập nhật dữ liệu lên giao diện từ đối tượng ViewModel.
2.2. ViewModel
Đây là class mới, bạn tạo một class với tên LoginViewModel
. Nó là ViewModel của LoginViewController
final class LoginViewModel { // MARK: - Properties var email: String = "" var password: String = "" // MARK: - Actions func login() { } }
ViewModel là ánh xạ kiểu dữ liệu từ View sang.
Nghĩa là:
- View có
Outlet
gì, thì ViewModel cóproperty
với kiểu dữ liệu tương ứng- UILable => String
- UIImageView => UIImage
- UITableView => Array
- ….
- View có
Action
gì, thì ViewModel cófunction
tương ứng - View có các function
request
gì, thì ViewModel có cácfunction
tương ứng- Các function fetch data
- Các protocol (delegate & delegate) của các UIControl
- …
Theo code ví dụ trên cho LoginViewModel
thì ta có:
- Property của nó sẽ là
email
với kiểu là String, tương ứng với UITextField cho emailpassword
với kiểu là String, tương ứng với UITextField cho password
- Method của nó sẽ là
login
, tương ứng với IBAction của UIButton Login
Tạm thời chúng ta cần như vậy, cái gì cần thiết thì mới triển khai.
Mở file LoginViewController.swift
- Tạo thêm 1 đối tượng ViewModel của nó
- Cập nhật lại giao diện tại
updateUI
- Tạo thêm IBAction cho
login
import UIKit class LoginViewController: BaseViewController { // MARK: - Properties @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! var viewModel = LoginViewModel() override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - config override func setupUI() { super.setupUI() self.title = "Login" updateView() } override func setupData() { } func updateView() { emailTextField.text = viewModel.email passwordTextField.text = viewModel.password } // MARK: - Actions @IBAction func loginButtonTouchUpInside(_ sender: Any) { print("Login") } }
2.3. Model
Ta có 1 model đơn giản dùng để đọc và lưu dữ liệu cho chương trình. Tạo 1 file với tên là DataManager
. Tham khảo đoạn code sau:
- Có 1 singleton để dùng
class DataManager { //singleton private static var sharedDataManager: DataManager = { let dataManager = DataManager() return dataManager }() class func shared() -> DataManager { return sharedDataManager } // init private init() {} //open database func read() -> (String, String) { let email = UserDefaults.standard.string(forKey: "email") ?? "" let password = UserDefaults.standard.string(forKey: "password") ?? "" return (email, password) } //save database func save(email: String, password: String) { UserDefaults.standard.set(email, forKey: "email") UserDefaults.standard.set(password, forKey: "password") UserDefaults.standard.synchronize() } }
Tạm thời chúng ta sử dụng
UserDefault
là Database cho chương trình.
Về Model thì nó cũng tương tự như Model trong bài MVC, phần này chúng ta không cần giải thích thêm nhiều.
2.4. Tóm tắt
- Lớp View:
- Bao gồm view và view controller
- Chuyên xử lí:
- Load giao diện người dùng
- Xử lý layout, animation, transition giữa view và window
- Nhận tương tác người dùng
- …
- Lớp ViewModel:
- Lớp trung gian giữa view và model.
- Nó chứa các mã lệnh cần thiết giúp cho việc thực hiện data binding, command:
- Format định dạng thể hiện của dữ liệu
- Gửi network request
- …
- Lớp Model:
- Là lớp giúp truy xuất và thao tác trên dữ liệu thực sự.
3. Nguyên tắc
- Thực tế, việc hoạt động của mô hình MVVM không chỉ dừng lại ở 1 model – 1 view – 1 view model
- Lưu ý:
- Từ ‘controller’ hay ‘view’ được đề cập ở đây là thành phần con của lớp view trong mô hình MVVM.
- Controller detail là controller cho detail từng item trong danh sách
- Một số nguyên tắc phụ trợ khi áp dụng mô hình MVVC:
- View nhận sự kiện của người dùng tác động, rồi truyền về controller (không phải truyền trực tiếp cho view model)
- View model không chứa các xử lý về UI (không import UIKit)
- Khởi tạo view model bằng 2 cách:
- Bên trong controller, khi các controller ở vị trí root hoặc không phải controller detail
- Bên ngoài controller, khi các controller là các controller detail
- Một view model có thể chứa một hoặc nhiều view model khác (root chứa detail)
- Một view model có thể được khởi tạo từ 1 view model (detail được khởi tạo từ root)
- Một view model có thể được dùng cho nhiều controller (view model là sự ánh xạ của 1 đối tượng của model lên view, trường hợp nhiều controller dùng chung 1 loại đối tượng model và tương đồng về mặc hiển thị, thì có thể dùng chung view model)
- Controller chịu trách nhiệm update dữ liệu lên view (không phải view model)
- Controller nhận các tín hiệu update dữ liệu từ view model bằng 2 cách:
- Callback (closure & protocol)
- Observer (notification, KOV)
4. Hoạt động
Chúng ta sẽ dựa vào sơ đồ này để tìm hiểu cách hoạt động của mô hình MVVM.
4.1 Binding
Cơ bản là cách tạo ra cầu nối dữ liệu giữa View và ViewModel.
Có nhiều cách được sử dụng ở đây. Nếu bạn chịu khó tìm tòi thêm thì có thể bắt gặp RxSwift
được dùng để phục vụ việc binding
dữ liệu. Trong bày này, chúng ta dùng cách thủ công truyền thống từ ngàn đời nay là:
Gán giá trị.
Tập trung vào mối quan hệ dữ liệu giữa View và ViewModel. Đầu tiên là dữ liệu, thay vì đợi chờ người dùng tự nhập dữ liệu cho chương trình. Thì ViewModel có thể cũng cấp trực tiếp. Rất có ý nghĩa cho việc UnitTest.
Chuyển sang ví dụ cho nhanh hiểu. Mở file LoginViewModel
và code:
// MARK: - Properties var email: String = "ví dụ cho emnail" var password: String = "ví dụ cho mật khẩu"
Gán cho 2 properites này giá trị. Và build app để kiểm tra:
Làm cho nó chuyên nghiệp hơn tý. Tiếp tục với file LoginViewModel
, viết thêm 1 hàm khởi tạo:
init(email: String, password: String) { self.email = email self.password = password }
Sang file LoginViewController
, edit lại đối tượng viewmodel
của nó 1 tí
var viewModel = LoginViewModel(email: "email nè", password: "mật khẩu nè")
Kết quả cũng tương tự như trên.
4.2. Request
Đại diện cho việc request thì ta sẽ lấy ví dụ cho IBAction
nhấn vào UIButton Login của màn hình LoginViewController.
Mở file LoginViewModel
, tiến hành edit function login
func login(email: String, password: String, completion: (Bool) -> ()) { if email == "" || password == "" { completion(false) } else { completion(true) } }
Giải thích:
- Tham số
email
vàpassword
, kiểu String completion
là một closure. Với 1 tham sốBool
, để trả về thành công hay thất bại
Mở file LoginViewController
lên, edit IBAction của nút Login
@IBAction func loginButtonTouchUpInside(_ sender: Any) { let email = emailTextField.text ?? "" let password = passwordTextField.text ?? "" viewModel.login(email: email, password: password) { (done) in if done { print("ĐĂNG NHẬP THÀNH CÔNG") } else { print("ĐĂNG NHẬP THẤT BẠI") } } }
Giải thích:
- Khi nhấn vào nút Login thì function IBAction sẽ được thực thi
- Đối tượng
viewmodel
của ViewController sẽ gọi functionlogin
của nó. - Tham số
email
&password
được lấy trực tiếp từ giao diệncompletion
được gán bằng 1 closure
- Khi
viewmodel
thực thi xonglogin
. Thì ViewModel sẽ gọi tới closurecompletion
để thông báo lại cho ViewController biết ràng “nhiệm vụ đã xong” - ViewController nhận được thông báo từ ViewModel thông qua
completion
thì xử lí dữ liệu theo như đã cài đặt.
Ví dụ trên chỉ đơn giản là mô tả cho bạn thấy luồng sự kiện được xử lí như thế nào. Còn trong thực tế, thì sẽ phức tạp hơn rất nhiều. Khi mà việc login sẽ phải chờ request tới các API/Webservice. Tuy nhiên, bản chất vẫn là vậy.
4.3. Call back
Là sự phản hồi hay đáp trả của ViewModel cho View hay ViewController biết.
Chúng ta có 3 cách phổ biến nhất cho callback này:
- Protocol (Delegate & Datasource)
- Closure
- Observer (notification, KOV)
4.3.1. Protocol
Với cách dùng Protocol thì áp dụng bài Delegation Pattern. Trong đó:
- ViewModel
- Khai báo 1
protocol
với các function mong muốn (như login) để gởi dữ liệu và thông báo đi - Tạo thêm 1 property là
delegate
, để gọi các function trên
- Khai báo 1
- ViewController
- Implement protocol trên
- Định nghĩa lại các function trong protocol
- Gán
delegate
của viewmodel bằng chính bản thân của ViewController
Cách này mình xin phép bỏ qua, vì nó đơn giản. Tuy nhiên, bạn nên dùng nó cho việc hiện thị gọi các custom view
hay thay đổi điều hướng màn hình.
4.3.2. Closure
Cách này là ví dụ ở trên cho phần request
. Tuy nhiên, chúng ta sẽ nâng cấp thêm cho nó. Tiếp tục mở file LoginViewModel
và code thêm.
- Tạo thêm 1 enum để trả kết quả của Login về với 2 trạng thái
success
: trạng thái thành côngfailure
: trạng thái thất bại. Gởi kèm về:Bool
: để biết trạng thái có lỗiString
: để biết cụ thể lỗi gì
// MARK: - enum enum LoginResult { case success case failure(Bool, String) }
- Định nghĩa một closure chuyên phục vụ cho
request
login. Closure bao gồm:- Một tham số kiểu
LoginResult
- Kiểu trả về là
Void
- Một tham số kiểu
// MARK: - typealias typealias Completion = (LoginResult) -> Void
- Chỉnh sửa lại function
login
- Chú ý chỗ gọi
completion
- Nếu thành công thì sẽ xoá dữ liệu của email và password
- Chú ý chỗ gọi
// MARK: - Actions func login(email: String, password: String, completion: Completion) { if email == "" || password == "" { //callback completion(.failure(true, "Mật khẩu hoặc email rỗng.")) } else { //cập nhật dữ liệu self.email = "" self.password = "" //callback completion(.success) } }
Tiếp tục tiến sang file LoginViewController
, tại IBAction của nút Login.
@IBAction func loginButtonTouchUpInside(_ sender: Any) { let email = emailTextField.text ?? "" let password = passwordTextField.text ?? "" // tao gia tri cho closure let complete: LoginViewModel.Completion = { (result) in switch result { case .success: //làm cái gi đó print("ĐĂNG NHẬP THÀNH CÔNG") self.updateView() case .failure(let isError, let errorMsg): if isError { print("ĐĂNG NHẬP THẤT BẠI") print(errorMsg) } } } // goi ham viewModel.login(email: email, password: password, completion: complete) }
Giải thích
- Phần sử lí callback được gán vào tham số của hàm
login
của đối tượngviewmodel
- Sử dụng
switch case
, để xử lí cho các trạng thái của callback
Thêm chút thi vi cho trường hợp thành công
case .success: //làm cái gi đó print("ĐĂNG NHẬP THÀNH CÔNG") self.updateView() //thay đổi root let scene = UIApplication.shared.connectedScenes.first if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) { sd.changeScreen(type: .tabbar) }
4.3.3. Observer
Phần này để hẹn ở các bài sau hoặc bài nâng cao hơn.
4.5. Fetch Data
Đây cũng chính là 1 dạng request dữ liệu. Thay vì nhận lệnh trực tiếp từ người dùng. Nó sẽ âm thầm thực hiện, nhằm lấy dữ liệu từ Model là chủ yếu. Nhiệm vụ của nó:
Lấy dữ liệu từ Database hoặc API/Webservice.
Phần này, chúng ta kết hợp các kiến thức lại với nhau. Đầu tiên thì lưu dữ liệu sau khi đăng nhập thành công. Mở file LoginViewModel
và edit thêm function login
.
func login(email: String, password: String, completion: Completion) { if email == "" || password == "" { //callback completion(.failure(true, "Mật khẩu hoặc email rỗng.")) } else { //cập nhật dữ liệu self.email = "" self.password = "" //save data DataManager.shared().save(email: email, password: password) //callback completion(.success) } }
Tới đây, thì lớp DataManager
khai báo ở trên, được sử dụng để lưu dữ liệu. Lưu 2 giá trị email và password lại. Tiếp tục, tạo mới 1 file HomeViewModel
là ViewModel cho HomeViewController
. Trong đó:
- 2 properties là email và password
- 1 function
fetchData
để lấy dữ liệu từDataManager
- lưu trữ dữ liệu fetch được vào 2 properties của ViewModel. Nhằm phục vụ cho việc hiển thị dữ liệu lên giao diện.
class HomeViewModel { var email: String = "" var password: String = "" func fetchData(completion: (Bool, String, String) -> ()) { let data = DataManager.shared().read() let email = data.0 let password = data.1 if email != "" || password != "" { //lưu trữ dữ liệu self.email = email self.password = password //callback completion(true, email, password) } else { //callback completion(false, "", "") } } }
Class này thì cũng không quá phức tạp lắm. Bạn tiếp tục mở file HomeViewController
và tiến hành edit nó với việc thêm đối tượng viewmodel
.
class HomeViewController: UIViewController { @IBOutlet weak var emailLabel: UILabel! var viewmodel = HomeViewModel() override func viewDidLoad() { super.viewDidLoad() title = "Home" fetchData() } func updateUI() { emailLabel.text = viewmodel.email } func fetchData() { viewmodel.fetchData { (done, email, password) in if done { self.updateUI() } else { print("LỖI") } } } }
Giải thích:
fetchData
được gọi lúc đầu tiên khi Home hiện ra- Sử dụng đối tượng
viewmodel
của Home, tiến hành lấy dữ liệu - Chờ
callback
của ViewModel sau khi lấy dữ liệu thành công. - Kiểm tra dữ liệu, nếu thành công thì sẽ gọi
updateUI
để hiển thị dữ liện lên giao diện
Build ứng dụng và test với dữ liệu email là abc@gmail.com
. Xem kết quả và chúc bạn thành công!
OKE, xong phần
fetchData
.
Tới đây thì mình xin kết thúc bài dài này. Với các điểm lý thuyết nêu ở trên thì bạn cũng đã có cái nhìn sơ bộ về mô hình MVVM trong iOS Project. Mã nguồn của bài các bạn tham khảo tại đây.
Tạm kết
- Định nghĩa về MVVM
- Thành phần và hoạt động của mô hình
- Tương tác giữa các thành phần trong mô hình
- Callback
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
- Lập trình hướng giao thức (POP) với Swift
You may also like:
Archives
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)