Contents
Chào bạn đến với seri Lập trình iOS cho mọi người. Bài viết này sẽ giúp bạn tìm hiểu về mô hình được sử dụng phổ biến nhất trong lập trình iOS. Đó là mô hình MVC ( Model – View – Controller).
Nếu bạn chưa tìm hiểu về Swift thì có thể tham khảo các bài viết sau:
Bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Định nghĩa
Trong lập trình nói chung, khi phát triển một dự án thì số lượng mã nguồn sẽ lớn lên. Và đi kèm với nó là nhiều đối tượng với nhiều mục đích sử dụng khác nhau. Điều này sẽ gây ra sự khó khăn trong việc quản lý các đối tượng. Chúng cần phải được cấu trúc và tổ chức theo một số luật
nhất định. Các luật này sẽ dần hình thành nên một cái, được gọi là Mô hình.
Mô hình của dự án sẽ quyết định kiến trúc gì được sử dụng, để phù hợp với yêu cầu của dự án. Cấu trúc của nó sẽ được chia theo sự phân loại các đối tượng trong mô hình đó. Việc thiết kế cấu trúc của mô hình sẽ tạo nên các mẫu hay còn gọi là Design Pattern.
Đối với lập trình iOS, mô hình được sử dụng nhiều nhất là Model – View – Controller (MVC). Đây là mô hình phổ biến nhất được Apple sử dụng và hầu hết các lập trình viên iOS. Có thể chúng không phải tối ưu nhất, nhưng chúng được xem là cốt lõi của toàn bộ ứng dụng/hệ thống … Các mô hình phát triển hơn sau này cũng dựa vào nó là chính, như MVVM, MVP, MV*, VIPER …
Thành phần
- Model: chính là ứng dụng của bạn nhưng là những phần không được hiện ra
- View: Là tấm gương phản chiếu của Controller
- Controller: Chính phần đảm nhiệm việc xử lý logic, để hiển thị những gì của Model mà cho người dùng thấy (UI Logic) và tương tác với View
2. Hoạt động
Để mô tả sự hoạt động giữa các thành phần với nhau trong mô hình, thì mình sử dụng 1 project, với:
- 1 UIViewController có giao diện như hình sau
Tổ chức cấu trúc các thư mục đơn giản như sau:
- AppDelegate : chứa 2 file AppDelegate
- Controllers : chứa các file UIViewController
- Views: chứa các custom view
- Models: chứa các class xử lý dữ liệu
- Resource: chứa ảnh, font, âm thanh ….
2.1. View
View ở trong iOS thì bao gồm nhiều loại. Hiểu đơn giản thì chính là View của View Controller. Đối tượng này là thuộc tính của class UIViewController. Nó được trỏ tới file *.xib, nên có thể hiểu file *.xib là View.
Ngoài ra, chúng ta còn có các Custom View. Chúng được tạo ra vào add vào View của View Controller. Khi đó, View sẽ bao gồm các file *.swift và *.xib.
Nhiệm vụ chính của View:
- Hiển thị dữ liệu từ Controller
- Truyền sự kiện của người dùng đến Controller
Trong view, có chứa nhiều control UI. Nó những thực thể vô định và có cấu trúc tổ chức. Nên để xác định cách liên lạc từ View về Controller thì cần tạo cho chúng nhưng mục tiêu (target
) để chúng hướng đến. Vậy để liên lạc giữa View và Controller thì chúng ta xác định các IBOutlet
cho các thành phần của view.
Xem code ví dụ
class HomeViewController: UIViewController { @IBOutlet weak var aTextField: UITextField! @IBOutlet weak var bTextField: UITextField! @IBOutlet weak var resultTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() } }
Một điều chúng ta cần chú ý:
Với View thì chúng ta có rất nhiều các control trong đó. Và chúng ta không cần thiết phải xác định hết tất cả chúng nó.
Sẽ có những control, mà chúng ta lấy sự kiện thì không cần thiết phải quản lý chúng bằng IBOutlet
.
Bạn cần những control nào để hiển thị dữ liệu trên View thì hay tạo
IBOutlet
cho nó.
Tiếp theo là cách để hiển thị dữ liệu lên View. Ta lại tham khảo tiếp đoạn code sau:
override func viewDidLoad() { super.viewDidLoad() aTextField.text = "10" bTextField.text = "7" }
Đơn giản, chỉ là xét trực tiếp giá trị cho chúng. Có thể tạo các function chung để xét giá trị cho các IBOutlet. Làm như vậy thì sẽ quản lý tập trung việc hiển thị dữ liệu lên View. (update ở sau cùng của bài)
2.2. Controller
Controller trong iOS chính là đối tượng thể hiện của class UIViewController hay các sub-class của UIViewController. Và là thành phần trung tâm của cả mô hình. Controller chịu các trách nhiệm sau:
- Nhận các sự kiện từ người dùng từ View
- Truyền các dữ liệu lên View
- Xử lí dữ liệu
- Quản lý các đối tượng, models, views
- Quản lý các Controller khác
Còn nhiều việc mà Controller có thể làm được. Ở phần View thì ta đã thấy được 1 chiều từ Controller tới View thông qua có IBOutlet
. Vậy còn chiều từ View tới Controller sẽ như thế nào?
Theo trên, View chỉ cần xác định target
cho đối tượng control nào, thì sẽ truyền các sự kiện của người tới đúng đối tượng control đó. Ta có thể bắt được chúng tại Controller với 2 cách:
- IBAction
- addTarget
IBAction đây là các phổ biến nhất cho chiều liên lạc từ View tới Controller. Bạn tham khảo tiếp đoạn code sau để lấy sự kiện của button (+)
@IBAction func add(_ sender: Any) { //code here }
Với IBAction
thì chúng ta không nắm giữ control đó trong Controller và khỏi lo quản lý nó.
addTarget đây là phương pháp code chay để nhận sự kiện từ View cho Controller.
import UIKit class HomeViewController: UIViewController { @IBOutlet weak var aTextField: UITextField! @IBOutlet weak var bTextField: UITextField! @IBOutlet weak var resultTextField: UITextField! //1 @IBOutlet weak var subButton: UIButton! override func viewDidLoad() { super.viewDidLoad() aTextField.text = "10" bTextField.text = "7" //2 subButton.addTarget(self, action: #selector(sub), for: .touchUpInside) } @IBAction func add(_ sender: Any) { //code here } //3 @objc func sub() { //code here } }
Trong đó:
- Xác định control trên View, đây là điều bắt buộc.
- Thêm
target
cho control. Chú ý các giá trị truyền cho các tham số - Function sẽ nhận được sự kiện.
2.3. Xử lí dữ liệu
Việc xử lí dữ liệu là một luồng xuyên suốt, bắt đầu từ việc xác định được function sẽ nhận sự kiên của người dùng tác động lên View. Sự kiện đó truyền tới Controller. Chúng ta, cần thực hiện theo các bước sau:
Bước 1: bạn cần lấy được dữ liệu. Việc này cũng khá đơn giản, khi mà Controller đã có sự liên kết với View thông qua các IBOutlet
từ trước.
@IBAction func add(_ sender: Any) { let a = Int(aTextField.text!) ?? 0 let b = Int(bTextField.text!) ?? 0 }
Bước 2: là phần xử lí dữ liệu. Với ví dụ trong bài, thì lại khá là đơn giản, chỉ cộng 2 số a
và b
@IBAction func add(_ sender: Any) { let a = Int(aTextField.text!) ?? 0 let b = Int(bTextField.text!) ?? 0 let result = a + b }
Bước 3: hiển thị dữ liệu lên View. Cũng như việc lấy dữ liệu, thì các control trên View đã được liên kết với Controller thông qua IBOutlet
từ trước. Nên việc hiển thị chỉ là sét giá trị cho control.
@IBAction func add(_ sender: Any) { let a = Int(aTextField.text!) ?? 0 let b = Int(bTextField.text!) ?? 0 let result = a + b resultTextField.text = "\(result)" }
Kết quả của ví dụ:
Ví dụ trên chỉ mô tả lại các bước cần thiết trong việc xử lí dữ liệu. Còn với bài toán thực tế thì phức tạp hơn nhiều.
2.4. Model
Model là toàn bộ ứng dụng, trừ phần hiển thị.
Khái niệm Model thì khá là lớn, chứ không đơn giản chỉ dừng lại ở việc tương tác với dữ liệu hay Database. Trong iOS, thì Model tạm thời được chia ra các loại sau:
- Object models
- Là các class cho các thực thể trong ứng dụng, như User, Post, Friend ….
- Nó là thể hiện cho phần dữ liệu lưu trữ. Trung gian cho việc tương tác giữa các phần với nhau trong ứng dụng.
- Service models
- Liên quan tới các dịch vụ được sử dụng, như:
- Location
- Database
- Connect API
- Date time
- …
- Liên quan tới các dịch vụ được sử dụng, như:
- Logic models
- Phần xử lí thuần, tương tác giữa các phần, tính toán, extention … mang tính chất chung, chứ không phụ thuộc theo bất cứ yêu cầu gì.
- Business models
- Là phần dựa theo yêu cầu của khách hàng/dự án.
Quay lại ví dụ trên thì với phần Model, chúng ta tạo tiếp 1 class với tên là Calculator
. Nhiệm vụ của nó là xử lí các phép toán. Tham khảo code sau:
import Foundation final class Calculator { func add(a: Float, b: Float) -> Float { return a + b } func sub(a: Float, b: Float) -> Float { return a - b } func mul(a: Float, b: Float) -> Float { return a * b } func div(a: Float, b: Float) -> Float { if b == 0 { return 0 } else { return a / b } } }
Trong mô hình MVC của iOS, thì chúng ta sẽ tách phần xử lí dữ liệu ở Controller ra. Giao trách nhiệm này cho Model. Vì vậy ta quay lại bước 2 trong phần Xử lí dữ liệu (ở trên).
@IBAction func add(_ sender: Any) { let a = Float(aTextField.text!) ?? 0 let b = Float(bTextField.text!) ?? 0 //1 let calculator = Calculator() //2 let result = calculator.add(a: a, b: b) resultTextField.text = "\(result)" }
Giải thích:
- Tạo ra một đối tượng Model, phụ trách việc xử lí thông tin
- Lấy kết quả sau khi đã xử lí xong, dùng nó để hiển thị lên View.
Run project và cảm nhận kết quả!
Tới đây, thì bạn đã hiểu sơ sự hoạt động tương tác giữa các thành phần trong mô hình MVC của iOS. Ngoài ra, bạn cần tối ưu chúng hơn, như:
- Lưu trữ dữ liệu từ View
- Quản lí hiển thị dữ liệu cho các control
- Các cách tương tác giữa model và controller
- Việc truyền sự kiện từ các Custom view tới các Controller
- …
Tham khảo full code cho các phần cần cải thiện với ví dụ của bài viết.
HomeViewController
import UIKit class HomeViewController: UIViewController { //outlet @IBOutlet weak var aTextField: UITextField! @IBOutlet weak var bTextField: UITextField! @IBOutlet weak var resultTextField: UITextField! //data var a : Float { get { return Float(aTextField.text!) ?? 0 } } var b : Float { get { return Float(bTextField.text!) ?? 0 } } var result: Float = 0 { didSet { resultTextField.text = "\(result)" } } //model var calculator = Calculator() override func viewDidLoad() { super.viewDidLoad() } //actions @IBAction func add(_ sender: Any) { result = calculator.add(a: self.a, b: self.b) } @IBAction func sub(_ sender: Any) { result = calculator.sub(a: self.a, b: self.b) } @IBAction func mul(_ sender: Any) { result = calculator.mul(a: self.a, b: self.b) } @IBAction func div(_ sender: Any) { result = calculator.div(a: self.a, b: self.b) } }
Calculator
import Foundation final class Calculator { func add(a: Float, b: Float) -> Float { return a + b } func sub(a: Float, b: Float) -> Float { return a - b } func mul(a: Float, b: Float) -> Float { return a * b } func div(a: Float, b: Float) -> Float { if b == 0 { return 0 } else { return a / b } } }
3. Nguyên tắc trong MVC
Để đảm bảo cho mô hình luôn đúng. Tránh sai lệch trong quá trình phát triển, thì có một vài nguyên tắc như sau cần được tuân thủ và tuân thủ nghiêm ngặt.
Nguyên tắc 1:
- Controller làm trung gian tương tác giữa Model và View
- View và Model không tương tác được với nhau
Nguyên tắc 2:
- View không xử lý tính toán dữ liệu
- Các tính toán logic sẽ được thực hiện ở Controller
Nguyên tắc 3:
- Controller có thể nói chuyện trực tiếp với model
- Model không liên lạc trực tiếp với controller, mà thông qua các interface hay notification
Nguyên tắc phụ:
- 1 controller có thể quản lý nhiều controller khác
- 1 model có thể phục vụ nhiều controller khác nhau
- 1 controller có thể có quản lý nhiều view
- 1 view chỉ có thể bị 1 controller quản lý
- Khi 1 view được sử dụng ở nhiều controller, thì khi đó ngữ nghĩa của nó là một model (đối tượng là view)
Các nguyên tắc này không quá cứng nhắc. Nên dựa vào yêu cầu thực tế mà có ứng xử cho phù hợp.
4. Hạn chế
Vấn đề của UIViewController chính là nó tập trung xử lý nhiều nhiệm vụ trong nó như:
- quản lý view
- animation
- nghiệp vụ logic trong app
- hiển thị
- điều hướng
- …
Việc này có thể không quá bận tâm nhiều đối với những ứng dụng nhỏ. Tuy nhiên, trong quá trình phát triển những ứng dụng lớn, khối lượng code xử lý trong UIViewController khá là nhiều.
Khiến chúng ta cảm thấy khó theo dõi trong quá trình code, quá trình test và debug cũng mất khá nhiều thời gian, khả năng mở rộng và tái sử dụng code cũng không được linh hoạt. Điều này khiến chúng ta suy nghĩ tới kiến trúc ứng dụng mới để khắc phục những vấn đề trên.
Bên cạnh đó, UnitTest là vấn đề nan giải trong mô hình MVC. Các thành phần liên kết khá chặt chẽ với nhau. Dẫn tới việc kiểm thử bằng dữ liệu giả là điều không thể. Nên với các dự án lớn, thường xuyên thay đổi yêu cầu thì việc kiểm tra lại toàn bộ mã nguồn sẽ tốn thời gian. Khó trong việc update, cũng như phát triển thêm các tính năng.
Để khắc phụ các hạn chế trên thì chúng ta sẽ nâng cấp mô hình trong iOS lên một mô hình mới. Đó là
MVVM
, phần này sẽ được cập nhật trong thời gian tới.
Cảm ơn bạn đã đọc bài viết này!
Tạm kết
- Tìm hiểu về mô hình MVC
- Các thành phần trong MVC
- Hoạt động của mô hình
- Điểm hạn chế của mô hình
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)