Contents
Chào bạn đến với Fx Studio. Cũng khá lâu rồi thì chúng ta mới tiếp tục một chủ đề về với iOS truyền thống. Chủ đề hôm nay sẽ là sự phân tích giữa 2 phương pháp truyền tải dữ liệu (Passing Data) trong iOS. Đó là Protocol vs. Closure.
Tất nhiên, bạn cần phải nắm rõ về Protocol & Closure trước đã. Còn nếu như bạn đã quên thì có thể quay lại 2 bài viết dưới đây để ôn tập thêm chút kiến thức trước khi vào bài viết.
Nếu như mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 12
- iOS 13.x
- Swift 5.x
Về demo code cho bài viết, bạn hãy sử dụng một iOS Project. Và cũng không cầu quá phức tạp. Chúng ta sẽ thực hiện các ví dụ đơn giản trên một ViewController áp dụng mô hình MVVM và không sử dụng Storyboard. Dành cho bạn nào quên hoặc không biết các khái niệm trên thì có thể truy cập link sau:
1. Mục đích
Về chủ đề của bài viết Protocol vs. Closure lần này là Passing Data. Tuy nhiên, mình muốn dùng nó làm câu chuyện dẫn đường để hướng tới 2 mục đích khác. Đó là:
- Cách sử dụng
- Sự tương đồng
1.1. Cách sử dụng
Hầu như tất cả các bạn Dev iOS đều sử dụng được Protocol và Closure rồi. Nhưng đó chỉ là các cú pháp mà bạn được học. Còn lúc bạn vào thực chiến thì vẫn là câu chuyện dài …
Đời không như là mơ …
Nhất là các bạn newbie khi mới vào dự án sẽ gặp rất nhiều khó khăn. Và việc bạn có thể làm tốt nhất chính là copy
và copy
. Nhưng về lâu về dài, nó dần ăn mòn đi sự sáng tạo trong tư duy của bạn. Bạn hãy đặt câu hỏi là:
Khi nào chúng ta dùng Protocol, khi nào dùng Closure?.
Nếu bạn lúng túng để trả lời chúng, thì đây chính là lúc bạn nên ngồi xuống và ôn tập lại kiến thức cơ bản rồi. Mọi thức sẽ được phát triển tốt, nếu chúng được xây dựng trên một nền móng vững chắc.
Cách sử dụng mà bài viết hướng tới là cách bạn triển khai Protocol hoặc Closure đối với bài toán mà bạn đang gặp phải. Chứ không phải là cú pháp để viết tụi nó ra.
1.2. Sự tương đồng
Cũng có nhiều bạn newbie hầu như không nhận ra sự tương đồng giữa 2 phương pháp này. Đôi khi như là:
- Biết về Delegate, nhưng không biết Protocol là gì.
- Tương tác API thì sẽ dùng closure.
- Gặp sự lúng túng khi khai báo một closure với một kiểu tự định nghĩa cho closure đó.
- Chưa biết cách triển khai được một DataSource
- ….
Tuy nhiều vấn đề như vậy, nhưng chúng ta sẽ tìm ra sự tương đồng của việc sử dụng hai thế lực trên. Mục đích giúp cho bạn có thêm vũ khi và thêm sự lựa chọn. Tránh code một cách máy móc và rập khuôn.
2. Vấn đề
Nhằm để bạn đạt được 2 mục đích đề ra ở trên, thì chúng ta sẽ đi vào các vấn đề cần giải quyết. Và đây là 2 vấn đề cơ bản đầu tiên.
- Passing Data
- Call back
2.1. Passing Data
Mình dùng từ cho nó màu mè một chút thôi. Nhưng bản chất thật sự của nó chỉ là truyền tải dữ liệu giữa các đối tượng.
class AViewController() { let viewmodel = AViewModel() func viewDidLoad() { title = viewmodel.title } }
(Ví dụ: truyền dữ liệu từ ViewModel sang ViewController với giá trị cho thuộc tính title
)
Code trên chỉ thể hiện được một chiều đầu tiên là từ A -> B
. Chiều này rất đơn giản, khi mọi việc chỉ gán giá trị cho nhau. Chúng đơn giản, vì con trỏ B
được khởi tạo trong con trỏ A
. Nên A
có toàn quyền sinh, quyền sát cho B
.
Nhưng một nhược điểm hay cũng là một điều vi phạm trong tính chất của hướng đối tượng. Đó là:
Tính bao đóng.
Bạn muốn gán dữ liệu trực tiếp như vậy, thì mọi thứ phải là public
. Nếu triển khai code theo đúng như vậy, thì cuộc đời không được vui lắm. Mọi thứ sẽ không đảm bảo bảo mật và riêng tư. Các liên kết giữa các đối tượng quá nhiều, gây khó khăn cho việc nâng cấp hoặc mở rộng sau này.
Qua trên, bạn sẽ thấy nó tuy là một vấn đề đơn giản, nhưng chúng ta cũng cần phải tối ưu chúng.
2.2. Call back
Chiều từ A -> B
thì khá là dễ, nhưng chiều ngược lại B -> A
, thì thực sự là một vấn đề phức tạp. Vì con trỏ A
không có trong B
. Bạn không có quyền sử dụng nhưng gì mà A
khi bạn đang ở B
. Vậy phép gán thần thánh trên sẽ không sử dụng được.
À, mà nó chưa phải là phức tạp nhất. Phức tạp hơn nữa đó là sự phản hồi (hay gọi là Call back). Đây chính là việc thông báo lại cho đối tượng trước đó biết về kết quả của hành động được thực thi xong.
Và sự phản hồi này rất cần thiết khi bạn có nhiều đối tượng cùng hoạt động trong một Thread. Chúng hoạt động độc lập và hỗ trợ cho nhau. Như là mối quan hệ giữa ViewController và ViewModel. Hoặc khi các Custom View hoạt động và chúng sẽ phải trả lại dữ liệu cho ViewController.
Nói lý thuyết lang mang cũng khá dài rồi. Chúng ta qua phần tiếp theo là giải pháp để giải quyết hai vấn đề trên bằng Protocol và Closure.
3. Giải pháp
3.1. Cấu trúc
Chúng ta sẽ sử dụng một cấu trúc đơn giản trong mô hình MVVM với 2 thực thể sau:
- ViewController
- ViewModel
3.1.1. ViewController
ViewController chỉ đảm đương công việc truyền dữ liệu từ ViewModel và hiển thị chúng lên View (giao diện) của nó.
Demo cho bài viết thì ta có một ViewController với 4 phép tính (cộng, trừ, nhân, chia) 2 số. Và 2 bài toán được đưa ra là:
- Chuyển IBAction (do người dùng tác động) đến ViewModel để xử lý.
- Cập nhật lại kết quả từ ViewModel để hiển thị lên Label.
Và chúng ta vẫn phải đảm bảo được các tính chất cơ bản của Hướng đối tượng.
Chúng ta tham khảo code ban đầu cho ViewController như sau:
import UIKit class HomeViewController: UIViewController { // MARK: - Properties @IBOutlet weak var number1TextField: UITextField! @IBOutlet weak var number2TextField: UITextField! @IBOutlet weak var resultLabel: UILabel! // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() setupUI() setupData() } // MARK: - Config func setupUI() { title = "Protocol vs. Closure" } func setupData() { } // MARK: - Actions @IBAction func addButtonTouchUpInside(_ sender: Any) { } @IBAction func subButtonTouchUpInside(_ sender: Any) { } @IBAction func mulButtonTouchUpInside(_ sender: Any) { } @IBAction func divButtonTouchUpInside(_ sender: Any) { } }
3.1.2. ViewModel
ViewModel là thành phần chính trong mô hình MVVM. Nó sẽ chịu trách nhiệm lưu trữ & xử lý dữ liệu. Nhưng một vấn đề quan trọng mà ít được đề cập trong mô hình MVVM. Đó là:
- Sự phản hồi lại Action từ ViewController (Call back)
Và với bài toán đặt ra ở trên, thì ta có code tham khảo cho ViewModel như sau:
class HomeViewModel { // MARK: - Properties var number1: Int var number2: Int var result: Float = 0 // MARK: - init init(number1: Int, number2: Int) { self.number1 = number1 self.number2 = number2 } // MARK: - Data with Protocol func getData() { } // MARK: - Action with Protocol func add() { } func sub() { } func mul() { } func div() { } }
3.2. Protocol
Chúng ta bắt đầu bằng việc sử dụng Protocol để xử lý các vấn đề được nêu ra ở trên. Ta sẽ sử dụng 2 tuyệt kĩ sau:
- Delegate cho việc phản hồi dữ liệu cho ViewController
- DataSource cho việc lấy dữ liệu từ ViewController
3.2.1. Define
Ta sẽ bắt đầu từ việc định nghĩa 2 protocol. Bạn mở file HomeViewModel.swift và thêm đoạn code định nghĩa vào.
protocol HomeViewModelDataSource { func getNumber1(viewmodel: HomeViewModel) -> Int func getNumber2(viewmodel: HomeViewModel) -> Int } protocol HomeViewModelDelegate { func viewmodel(viewmodel: HomeViewModel, didFinishedWith number1: Int, number2: Int, result: Float) func viewmodel(viewmodel: HomeViewModel, didFaileddWith number1: Int, number2: Int, errorMessage: String) }
Phần định nghĩa này cũng không quá là khó. Chúng ta có các function getNumber1
và getNumber2
để lấy dữ liệu từ ViewController. Chúng đặt trong một protocol là DataSource. Đây là chiều đầu tiên trong Passing Data.
A -> B
Vậy đặc điểm của nó là gì?
- Đối tượng sử dụng (ViewModel) sẽ không nhận dữ liệu trực tiếp theo phương pháp gán thần thánh.
- Dữ liệu lúc nào cần thì sẽ lấy. Nó giúp cho class của bạn đa năng hơn, thay vì phải fix cứng với một class khác.
- Đảm bảo được các tiêu chí trong lập trình về sự liên kết giữa các đối tượng với nhau thông qua
input
&output
. - Function sẽ có kiểu giá trị trả về khác
Void
.
Một điều đáng buồn là việc áp dụng DataSource hầu như không được các dev & newbie sử dụng. Thay vì đó thì các bạn luôn truyền thẳng dữ liệu vào lúc khởi tạo. Nếu trong quán trình hoạt động mà cần sự thay đổi dữ liệu, thì gán lại từ đầu. Cách thực hiện này quá nhiều nguy hiểm.
Còn phần định nghĩa cho Delegate thì cũng quá quen thuộc rồi. Chúng ta cần 2 function cho 2 trường hợp thành công & lỗi. Delegate mang trách nhiệm phản hồi lại cho ViewController biết. Đây chính là chiều thứ 2 trong Passing Data
B -> A
Có một điều cần lưu ý trong việc định nghĩa các protocol. Đó là bạn phải khai báo thêm tham số với kiểu dữ liệu chính là class mà bạn sử dụng (trong ví dụ là ViewModel).
3.2.2. Implement
Việc implement chúng ta chia thành 2 phần tại 2 file. Phần thứ nhất, bạn mở file HomeViewController.swift và khai báo thêm các extension vào.
extension HomeViewController: HomeViewModelDataSource, HomeViewModelDelegate { func getNumber1(viewmodel: HomeViewModel) -> Int { if let number = Int(number1TextField.text!) { return number } else { return 0 } } func getNumber2(viewmodel: HomeViewModel) -> Int { if let number = Int(number2TextField.text!) { return number } else { return 0 } } func viewmodel(viewmodel: HomeViewModel, didFinishedWith number1: Int, number2: Int, result: Float) { resultLabel.text = "\(result)" } func viewmodel(viewmodel: HomeViewModel, didFaileddWith number1: Int, number2: Int, errorMessage: String) { resultLabel.text = errorMessage } }
Trong đó:
- Các function của DataSource thì bạn sẽ
return
giá trị về cho ViewModel. Các giá trị tương ứng với các TextField của ViewController. - Với Delegate thì sẽ hiển thị dữ liệu lên Label của ViewController.
Tiếp tục phần 2 là việc sử dụng các thể hiện của 2 Protocol trong ViewModel. Đây là nơi tập trung phần xử lý liệu. Bạn mở file HomeViewModel.swift và thêm 2 thuộc tính sau:
- Chú ý cả hai đều phải khai báo là optional.
var delegate: HomeViewModelDelegate? var dataSource: HomeViewModelDataSource?
Như vậy là ổn cho việc cài đặt Protocol cho 2 class. Chúng ta sang phần chính là cách áp dụng vào bài toán thực tế là như thế nào.
3.2.3. Passing Data
Để đảm bảo cho việc hoạt động giữa 2 class. Bạn phải xét các protocol của ViewModel chính là ViewController. Tại file ViewController bạn thêm đoạn code sau:
func setupData() { viewmodel.delegate = self viewmodel.dataSource = self }
Function setupData
sẽ được dùng cho các tác vụ liên quan tới dữ liệu vào lúc khởi tạo ViewController.
Bây giờ, ta thực việc việc lấy dữ liệu từ ViewController cho các thuộc tính của ViewModel thông qua DataSource. Bạn mở file ViewModel và tiến hành thêm đoạn code sau vào function getData
.
func getData() { if let dataSource = dataSource { number1 = dataSource.getNumber1(viewmodel: self) number2 = dataSource.getNumber2(viewmodel: self) } else { number1 = 0 number2 = 0 } }
Vì:
- Hai function của DataSource return về kiểu dữ liệu là
Int
. Do đó, ta gán trực tiếp vào 2 thuộc tínhnumber1
&number2
của ViewModel dataSource
là một Optional nên cần phải unwrap nó.
Bây giờ, bạn đã thực hiện xong việc lấy dữ liệu từ ViewController cho ViewModel. Đây là chiều thứ nhất.
3.2.4. Call back
Đây là sự phản hồi lại kết quả khi thực hiện một hành động nào đó. Trong ví dụ thì:
- ViewController sẽ chuyển hành động người dùng sang ViewModel thông qua các IBAction được cài đặt.
- ViewModel sẽ phản hồi kết quả cho ViewController. Sau đó, ViewController sẽ hiển thị chúng lên giao diện.
Tại file ViewModel, bạn sẽ phải triển khai logic các hành động. Chúng ta sẽ thử nghiệm hành động đầu tiên là cộng 2 số.
func add() { getData() // process result = Float(number1 + number2) // return if let delegate = delegate { delegate.viewmodel(viewmodel: self, didFinishedWith: number1, number2: number2, result: result) } }
Trong đó:
- Bước 1: lấy dữ liệu mới nhất từ ViewController, bằng cách gọi hàm
getData
. Lúc này DataSource sẽ thực hiện nhiệm vụ của nó. - Bước 2: tính toán (EZ!)
- Bước 3: phản hồi (call back) bằng cách sử dụng Delegate để gởi dữ liệu thông qua các tham số function của Protocol Delegate.
Sang file ViewController, thực hiện việc gọi ViewModel xử lý tính toán khi có sự kiện từ người dùng.
@IBAction func addButtonTouchUpInside(_ sender: Any) { viewmodel.add() }
Quá EZ, bạn thử build và xem kết quả nào!
Ta thử tiếp với việc Call back lỗi với trường hợp chia một số với 0
. Tại ViewModel, bạn triển khai tiếp function div()
như sau:
func div() { getData() if let delegate = delegate { // validate if number2 != 0 { // process result = Float(number1) / Float(number2) // return delegate.viewmodel(viewmodel: self, didFinishedWith: number1, number2: number2, result: result) } else { // return delegate.viewmodel(viewmodel: self, didFaileddWith: number1, number2: number2, errorMessage: "Input error!") } } }
Kết quả thực thi như sau:
Chúng ta đã thực hiện xong 2 công việc Passing Data & Call back bằng Protocol rồi. Và tiếp tục xem giải quyết bằng Closure sẽ như thế nào.
3.3. Closure
3.3.1. In function
Với Closure, bạn sẽ triển khai nó trực tiếp vào function mà không thông qua việc khai báo như Protocol. Bạn mở file ViewModel và tạo các function mới như sau:
func getData(number1Completed: () -> Int, number2Completed: () -> Int) { } func add(number1: Int, number2: Int, completed:(Float, String?) -> Void) { } func sub(number1: Int, number2: Int, completed:(Float, String?) -> Void) { } func mul(number1: Int, number2: Int, completed:(Float, String?) -> Void) { } func div(number1: Int, number2: Int, completed:(Float, String?) -> Void) { }
Cũng không quá khó, chỉ đơn giản là việc khai báo các tham số với kiểu dữ liệu là Closure mà thôi. Trong đó bạn chú ý tới 2 kiểu khai báo với giá trị trả việc
- Bằng
Void
chính là Call back (tương tự DataSource) - Khác
Void
chính là Passing Data (tương tự Delegate)
À, hiểu nôm na như vậy nha.
3.3.2. Passing Data
Chúng ta sẽ quan tâm tới function getData()
tại ViewModel. Bạn triển khai nó như sau:
func getData(number1Completed: () -> Int, number2Completed: () -> Int) { self.number1 = number1Completed() self.number2 = number2Completed() }
Vì cùng kiểu giá trị trả về là Int
, nên bạn có thể gán trực tiếp cho các thuộc tính của ViewModel. Tiếp theo, bạn sang file ViewController để xử lý việc truyền dữ liệu như thế nào.
// Example get data viewmodel.getData { () -> Int in if let number = Int(number1TextField.text!) { return number } else { return 0 } } number2Completed: { () -> Int in if let number = Int(number2TextField.text!) { return number } else { return 0 } }
Đoạn code trên bạn để ở bất kì đâu cũng được. Vì đặc tính khi sử dụng Closure là bạn sẽ implement nó tại bất kì đâu mà bạn muốn.
3.3.3. Call back
Giờ sang phần tiếp theo là phản hồi lại ViewController biết những kết quả từ ViewModel. Bạn mở file ViewModel và triển khai cho function sub()
, làm ví dụ đầu tiên.
func sub(number1: Int, number2: Int, completed:(Float, String?) -> Void) { self.number1 = number1 self.number2 = number2 self.result = Float(number1) - Float(number2) completed(result, nil) }
Trong đó:
- Các giá trị từ ViewController sẽ được truyền vào các tham số của function
sub()
- Việc phản hồi thông qua việc gọi Closure
completed
thực thi
À, chắc nhiều bạn sẽ thắc mắc sao nó không giống phương pháp trên với Protocol. Vì:
- Việc sử dụng Closure như là một DataSource lúc này là không cần thiết.
- Ta có thể truyền trực tiếp giá trị vào tham số của function.
Sang file ViewController, bạn sẽ phải gọi ViewModel xử lý hơi phức tạp hơn so với Protocol. Xem code ví dụ với IBAction cho việc trừ 2 số.
@IBAction func subButtonTouchUpInside(_ sender: Any) { if let num1 = Int(number1TextField.text!), let num2 = Int(number2TextField.text!) { viewmodel.sub(number1: num1, number2: num2) { (result, errorMessage) in if let error = errorMessage { resultLabel.text = "\(error)" } else { resultLabel.text = "\(result)" } } } }
Bạn thử build và kiểm tra kết quả nào.
3.3.4. Typealias
Ở trên, chúng ta đã đảm bảo giải quyết Passing Data & Call back với Closure rồi. Tuy nhiên, nếu tham số của Closure quá nhiều thì function của bạn trông rất là rối mắt. Đây cũng là nhược điểm của phương pháp dùng Closure. Dó đó, bạn phải khai báo một kiểu dữ liệu riêng cho các Closure của bạn.
Mở file ViewModel và thêm các khai báo sau:
typealias ResultCompletion = (Float, String?) -> Void typealias NumberCompletion = () -> Int
Sau đó, tiến hành cập nhật lại các function mà bạn đã triển khai với Closure.
func getData(number1Completed: NumberCompletion, number2Completed: NumberCompletion) { self.number1 = number1Completed() self.number2 = number2Completed() } func add(number1: Int, number2: Int, completed: ResultCompletion) { self.number1 = number1 self.number2 = number2 self.result = Float(number1) + Float(number2) completed(result, nil) } func sub(number1: Int, number2: Int, completed:ResultCompletion) { self.number1 = number1 self.number2 = number2 self.result = Float(number1) - Float(number2) completed(result, nil) } func mul(number1: Int, number2: Int, completed: ResultCompletion) { self.number1 = number1 self.number2 = number2 self.result = Float(number1) * Float(number2) completed(result, nil) } func div(number1: Int, number2: Int, completed: ResultCompletion) { if number2 != 0 { self.number1 = number1 self.number2 = number2 self.result = Float(number1) / Float(number2) completed(result, nil) } else { completed(0, "Input Error!") } }
Còn với ViewController thì không thay đổi gì hết. Với typealias
, bạn sử dụng Closure như một tham số/biến trong function.
Tới đây là hết rồi. Và bạn có thể checkout source code tại đây. Chúc bạn may mắn!
Tạm kết
- Cách triển khai Protocol & Closure trong project
- Sự tương đồng trong xử lý của Protocol & Closure với các bài toán thực tế
- Biết cách truyền tải dữ liệu (Passing Data) bằng Protocol & Closure
- Biết cách phản hồi dữ liệu (Call back) bằng Protocol & Closure
Okay! Tới đây thì mình xin kết thúc bài viết Protocol vs. Closure với phần đầu tiên là Passing Data. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
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!
13 comments
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- 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
You may also like:
Archives
- September 2024 (1)
- 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)
a cho e hỏi là tại sao với cách protocol thì tham số truyền vào có cả viewModel ạ? Tại e thấy truyền vào xong không sử dụng ạ.
À, đơn giản ví 1 class ViewModel có thể dùng cho nhiều ViewController. Và 1 ViewController có thể dùng 1 lúc nhiều ViewModel. Không nhất thiết là phải 1 ViewModel với 1 ViewController.
Truyền thêm không sử dụng, nhưng khi rơi vào những trường hợp như trên. Thì ta sẽ dùng để phân biệt thèn ViewModel náo phát delegate đi.
Em xem ví dụ các function UITableViewDelegate & DataSource. Và khi trong VC của em lại dùng nhiều nhiều hơn 2 tableview thì lúc đó em sẽ hiểu.
cho e hỏi là:
Về trường hợp một view nhiều viewModel thì e thấy: viewModel sẽ là sử lý logic cho view. Mỗi view sẽ có logic riêng. Nên e thấy mỗi view cần có một viewModel riêng tại từng thời điểm khác nhau.
Còn về trường hợp một viewModel cho nhiều view: Ad có thể làm một bài về vấn đề một viewModel dành cho nhiều view không ạ?
Hi em,
– Về trường hợp một view nhiều viewModel : em cần hiểu View ở đây mang nghĩa rộng hơn. Như ViewController cũng đc xem là view trong mô hình MVVM. Với nhiều ViewController phức tạp, như PageViewController thì nó có thể mang trong mình hiều ViewModel.
– Về trường hợp một viewModel cho nhiều view: điển hình cho trường hợp Custom View theo kiểu lập trình Protocol. View lúc này chĩ là một protocol với các method & properties cần thiết thôi. Các sub-class kế thừa lại nó thì phải implement protocol View kia. Hiển nhiên, đi kèm với nó là việc dùng chùng ViewModel.
Cảm ơn em đã bình luận về bài viết. Nếu có thắc mắc chi thì em cứ gởi cho anh. Thanks!
– Về trường hợp một viewModel cho nhiều view: cái trên anh giải thích về mặt cấu trúc hướng đối tượng.
Còn đúng hơn về mặt mô hình thì ta có 1 ViewModel của VC mà chứa 1 TableView thì nó cũng sẽ quản lý luôn dữ liệu của các Cell hoặc các ViewModel con mà nó tạo ra cho từng Cell 1.
Hoặc, do thói quen lười của dev. Có thể gom vào xử lý ở ViewModel của VC cho nhiều Custom View
kiến thức của e còn hạn chế nên chưa phân biệt được
e cảm ơn vì những chia sẻ của a. Hy vọng a sẽ cho ra nhiều ví dụ để giúp mọi người hiểu rõ hơn ạ
nếu chúng ta viết title = viewModel.getTitle() thì có vi phạm tính bao đóng không ạ?
Tính bao đóng áp dụng cho Hướng Đối Tượng. Đúng bản chất thì phải viết như em ở trên. Nhưng đôi lúc dev iOS nó lười quá nên thường gán trực tiếp đó em. Cũng như ngta vẫn hiểu ngầm trong MVVM thì 1 View (View & ViewController) đi kém với 1 ViewModel đó.
e cảm ơn
Thank you!!1
Theo mình thấy nếu viết Protocol thế này thì sẽ không thể mở rộng được vì Protocol này chỉ có thể sử dụng ở HomeViewModel
Hi bạn,
Bạn nói đúng rồi. Do phạm chủ đề bài viết này là về truyền dữ liệu bằng Protocol & Closure nên mình không đề cập tới việc mở rộng. Đơn giản là giao tiếp giữa 2 đối tượng A và B thôi à.