Contents
Chào bạn đến với Fx Studio. Bài viết này sẽ mở đầu cho một chương mới trong series SwiftUI Notes. Chương mới này sẽ tập trung về State & Data Flow trong SwiftUI. Cách dữ liệu được vận hành và hoạt động, các sự biến đổi giữa giao diện và dữ liệu. Và bài viết này sẽ đi vào khái niệm tổng quát mà thôi.
Ngoài ra, đây cũng được xem làm phần nâng cao của một trong số các bài viết cơ bản về SwiftUI. Bạn có thể tham khảo ở link dưới đây.
Còn nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Về mặt tool và version, các bạn tham khảo như sau:
-
- SwiftUI 2.0
- Xcode 12
Về mặt kiến thức, bạn cần biết trước các kiến thức cơ bản với SwiftUI & SwiftUI App. Tham khảo các bài viết sau, nếu bạn chưa đọc qua SwiftUI:
(Mặc định, mình xem như bạn đã biết về cách tạo project với SwiftUI & SwiftUI App rồi.)
1. Mô hình MVC
MVC cả là một tượng đài đối với giới lập trình nói chung và với iOS nói riêng. Ngay cả trong framework chính là UIKit hay AppKit, thì đều thể hiện hoặc thiết kế ra để sử dụng với mô hình MVC.
Dành cho bạn chưa biết, thì có thể tóm tắt các thành phần trong MVC như sau:
- Controller đóng vai trò chính và kết nối
- View là giao diện
- Model lưu trữ dữ liệu và trạng thái
Controller đóng vai trò trung tâm và trái tim của toàn bộ mô hình. Giữ mọi thứ đồng bộ với nhau giữa giao diện và dữ liệu. Ví dụ:
- Chuyển dữ liệu cho giao diện hiển thị
- Cập nhật dữ liệu khi giao diện có tác động hoặc bị thay đổi từ người dùng
- …
Vấn đề tồn tại
Trong bài viết này, ta sẽ chỉ tập trung vào mặt đồng bộ giữa dữ liệu và giao diện mà thôi. Các vấn đề khác của MVC tạm thời sẽ không bàn đến.
Bạn xem qua ví dụ sau cho một ViewController đơn giản nhất.
class ViewController: UIViewController { var name: String? @IBOutlet var nameTextField: UITextField! }
Nếu bạn không cho nameTextField
một dữ liệu để nó hiển thị, thì người dùng sẽ không bao giờ thấy được. Đó là nguyên nhân bạn cần thêm một thuộc tính String nữa, là name
. Lúc đó, việc truyền dữ liệu cho TextField sẽ như sau:
nameTextField.text = name
Việc lấy dữ liệu mới của TextField thì sẽ như thế này.
name = nameTextField.text
Bạn đã nhận ra được vấn đề gì chưa?
Vấn đề dễ thấy sẽ là:
- Quá nhiều đối tượng để lưu trữ và truyền dữ liệu
- Bạn sử dụng thêm Model để làm nơi lưu trữ dữ liệu, thì sẽ tăng thêm các biến và vùng nhớ để lưu trữ dữ liệu. Nhưng chưa chắc giao diện sẽ được cập nhật đúng.
Vấn đề khó thấy sẽ là:
- Trạng thái của dữ liệu chưa chắc là trạng thái của giao diện.
- Sự phát sinh thêm nhiều logic hoặc tách thêm các Model xử lý. Dẫn tới cồng kềnh
- Sự nhập nhằng trong chính nền tảng của Apple (AppKit & UIKit), khi View & Controller không tách biệt rõ ràng.
Tóm tắt
- Vì nó quá cồng kềnh nên nó là vấn đề chính
- Luôn phải đảm bảo cho UI & Model phải đồng bộ với nhau
- Trạng thái dữ liệu không phải lúc nào cũng đồng bộ với UI
- Từ View bạn có thể thay đổi trạng thái dữ liệu & model
- Rất dễ phát sinh lỗi
2. Một giao diện cũng là một chức năng
Ở trên, bạn sẽ đọc qua khái niệm trạng thái của giao diện. Và bạn thử suy nghĩ “sẽ như thế nào khi giao diện của bạn trở thành một chức năng (function).”
Sẽ giảm đi rất nhiều việc tạo thêm các biến để lưu trữ và truyền dữ liệu từ thành phần này sang thành phần khác. Loại bỏ đi các điều kiện kiểm tra. Như là cho phép 1 view được hiển thị hay không, hoặc phụ thuộc vào 1 hay 1 vài điều kiện nhất định.
Ví dụ: nếu đang kết nối API thì cờ là true
, thì hiển thị loading view
. Kết thúc kết nối thì cờ lại là false
, khi đó loadingView
sẽ biến mất.
Hay nhất, bạn không cần phải ghi nhớ việc thường xuyên cập nhật lại giao diện khi có sự thay đổi dữ liệu. Ví dụ: có nhiều chỗ trong màn hình có thể tương tác với API, thì phải đảm bảo việc cập nhật giao diện tại tất cả các điểm đó.
Ngoài ra,
- Giảm đi các tham chiếu và các tham chiếu của các closure đi khá nhiều
- View sẽ xuất hiện giống như với dữ liệu ban đầu và cập nhật giống như dữ liệu được cập nhật.
Những điểm trên thì không giúp bạn giỏi lên đâu, nếu bạn quản lý tốt các liên kết và cập nhật đồng bộ giữa các thành phần thì MVC vẫn là bá chủ. Nhưng mà thay đổi một chút để vui vẻ hơn, đơn giản hơn và ít bugs hơn.
Cần gì để thực hiện được điều này?
SwiftUI hội tụ đầy đủ các yếu tố mà bạn cận để làm một cuộc cách mạng
- Declarative : bạn không triển khai (implement) giao diện mà là khai báo chúng (declare)
- Functional : giao diện là một function của trạng thái
- Reactive: khi có thay đổi, giao diện sẽ tự động cập nhật
Ahihi!
3. Tổng quan về State & Data Flow
SwiftUI cung cấp một cách tiếp cận Declarative cho UI design. Bạn sẽ thiết kế nên sự phân cấp trong hệ thống các view và chỉ ra sự phụ thuộc giữa dữ liệu & View. Khi có tác động của người dùng thì SwiftUI sẽ cập nhật lại các phần giao diện bị ảnh hưởng. Framework này sẽ đảm đương hầu hết các công việc như một View Controller (lúc trước)
SwiftUI cung cấp cho bạn nhiều công cụ, như biến trạng thái, binding … để kết nỗi giữa dữ liệu và giao diện. Tất cả công cụ này giúp duy trì một thứ gọi là The single source of truth cho tất cả các dữ liệu trong app. Và giảm đi một phần logic của bạn viết.
Trong đó, các thành phần tạo nên thứ gọi là State & Data Flow thì như sau:
- Quản lý các trạng thái của UI trong chính View của nó bằng các thuộc tính State
- Kết nối với các dữ liệu model tham chiếu bên ngoài thông qua Observable Protocol và các thuộc tính ObserverObject
- Truy cập tới các đối tượng có thể quan sát được từ mô trường thông qua các thuộc tính EnvironmentObject
- Tạo ra một đối tượng có thể quan sát được ở trong View bằng thuộc tính StateObject
- Chia sẽ các tham chiếu tới The single source of truth (tương tự như các đối tượng tham chiếu quan sát được ở trên) thông qua các thuộc tính Binding
- Truyền dữ liệu đi trong toàn bộ app thông qua các biến Environment
- Truyền dữ liệu đi qua hệ thống phân cấp View bằng PreferenceKey
- Sử dụng FetchRequest để tương tác với CoreData
4. Sức mạnh của các Property Wrappers
Như đã nói ở trên, công cụ mà SwiftUI cung cấp cho bạn chính là các Property Wrappers. Giúp bạn quản lý dữ liệu của các biến State hay Binding. Để áp dụng thì khá là đơn giản, bạn chỉ cần thêm tên của Property Wrappers vào trước các thuộc tính của bạn.
Ví dụ, khi muốn biến một thuộc tính Bool thành một kiểu State với Property Wrappers như sau:
@State private var isVisible = true
Các thuộc tính mà được bọc với Property Wrappers thì sẽ mang các đặc tính của Property Wrappers đó. Các State & Data đó sẽ theo dõi các thay đổi dữ liệu. Khi bạn tham chiếu trực tiếp tới thuộc tính trong code, bạn có thể sử dụng trực tiếp được giá trị của nó.
Ví dụ code tham khảo tiếp nha.
if isVisible == true {
Text("Hello") // Only rendered when isVisible is true.
}
Ngoài ra, khi bạn truy cập giá trị của một Property Wrapper bằng tiền tố $
thì bạn sẽ tạo ra một tham chiếu Binding
. Là một kết nối 2 chiều giữa View khác (ở bên ngoài) và The single source of truth.
Ví dụ tiếp nữa nè.
Toggle("Visible", isOn: $isVisible) // The toggle can update the stored value.
Bạn yên tâm là chúng ta sẽ tìm hiểu tất cả Property Wrapper của SwiftUI trong các bài viết sau. Còn nếu bạn muốn tìm hiểu riêng về Wrapper trong Swift, thì có thể tham khảo tại đây.
Tạm kết
- Biết được các vấn đề còn tồn tại trong MVC
- Sự đồng bộ giữa trạng thái dữ liệu và giao diện của ứng dụng
- Tổng qua về State & Data Flow trong SwiftUI
- Sức mạnh về các Property Wrapper mang lại
Okay! Tới đây, mình xin kết thúc bài viết về State & Data Flow trong SwiftUI. Và 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!
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)