Contents
Chào bạn đến với Fx Studio. Nếu bạn là người theo dõi series RxSwift này của Fx Studio ngay từ đầu. Thì đó là cả một chặng đường dài. Và bài viết này sẽ mở đầu cho một miền đất mới, đó là RxSwift UIKit. Để nói đầy đủ một chút là sử dụng RxSwift trong framework UIKit.
Dành cho các bạn chưa đọc qua phần đầu tiên của series, thì mình đã tổng hợp hết vào một bài viết. Bạn tham khảo bài viết qua link sau:
Còn mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 11
- Swift 5.x
Bắt đầu từ bài viết này, chúng ta tạm thời chia tay em Playground và đến với iOS Project quen thuộc nha. Về project, mình khuyên bạn nên tự do sáng tạo. Các bài viết của mình mang tính chất tham khảo. Khi sáng tạo, bạn có nhiều bài toán của riêng bạn để giải quyết. Khi đó bạn sẽ học hỏi được nhiều hơn.
Và tất nhiên, Fx Studio vẫn sẽ có cho bạn một project tham khảo. Bạn có thể checkout project tham khảo này:
- Link: checkout
- Thư mục:
/Examples/BasicRxSwift
1. RxSwift vs. UIKit
Về việc sử dụng RxSwift trong UIKit, mình chỉ giới hạn trong phạm vi sử dụng RxSwift là 1 framework
. Về mặt này, RxSwift rất hữu ích cho bạn và nó giải quyết được rất nhiều vấn đề thường gặp trong lúc làm dự án iOS.
Mô hình hay kiến trúc cho project thì sẽ không được đưa vào trong các bài viết của phần RxSwift vs. UIKit này. Mà bạn hãy đọc bài viết với tâm tư tình cảm của một bạn iOS Developer mới vừa học xong iOS. Do đó, cũng có thể gọi phần 2 này là …
RxSwift cho mọi người
Các bài toán & vấn đề đặt ra trong phần này sẽ là:
- Vấn đề cơ bản mà 1 bạn dev iOS làm việc hằng ngày hay gặp
- Giải quyết các logic cơ bản
- Bước đầu áp dụng việc viết Model
- Không cần phải thay đổi nhiều thứ trong project hiện tại của bạn
- …
Với kiến thức cơ bản mà bạn tìm hiểu ở phần 1 RxSwift, thì đã đủ để cho bạn giải quyết tất cả các vấn đề ở trên đưa ra rồi. Và bạn chưa cần sử dụng tới RxCocoa hay bất cứ hàng nóng nào thêm đâu.
Qua giới thiệu, mình hi vọng bạn sẽ không cần phải lo lắng nhiều khi bắt đầu lại với RxSwift trong UIKit. EZ!
2. Setup
2.1. Project Demo
Vì project demo được sử dụng cho rất nhiều ví dụ nên nó khá là đồ sộ. Tuy nhiên, bạn chỉ cần để tâm vào các file mà mình đề cập tới mà thôi. Về project thì mình sử dụng như sau:
- Không sử dụng Storyboard (nếu bạn quên cách tạo thì xem ở đây).
- CocoaPod để cài đặt thư viện RxSwift (cách cài đặt thì xem ở đây).
Bạn hãy bắt đầu với file RegisterViewController.swift
. Nó cũng có giao diện khá đơn giản.
Giao diện đầu tiên bao gồm:
- UIImageView để sử dụng làm avatar
- 3 UITextField lần lượt cho username, password & email
- 2 UIButton để register & clear dữ liệu trong các textfield
Bạn hãy sáng tạo thêm nha!
2.2. Import
Mọi thứ sẽ bắt đầu bằng việc import
thư viện RxSwift vào trong file swift của bạn. Bạn hãy mở file RegisterViewController và thêm 2 dòng import sau đây vào.
import RxSwift import RxCocoa
Bạn cũng không cần để ý tới RxCocoa nha, vì đôi khi ta dùng một số class của nó. Còn về phạm vi bài viết thì mình sẽ cố gắng sử dụng tất cả RxSwift để giải quyết bài trên UIKit.
Và toàn bộ code của file RegisterViewController ban đầu như sau:
import UIKit import RxSwift class RegisterViewController: UIViewController { // MARK: Outlets @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var registerButton: UIButton! // MARK: Properties var avatartIndex = 0 // MARK: Life cycle override func viewDidLoad() { super.viewDidLoad() title = "Register" configUI() } // MARK: Config func configUI() { avatarImageView.layer.cornerRadius = 50.0 avatarImageView.layer.borderWidth = 5.0 avatarImageView.layer.borderColor = UIColor.gray.cgColor avatarImageView.layer.masksToBounds = true let leftBarButton = UIBarButtonItem(title: "Change Avatar", style: .plain, target: self, action: #selector(self.changeAvatar)) self.navigationItem.leftBarButtonItem = leftBarButton } // MARK: Actions @IBAction func register(_ sender: Any) { } @IBAction func clear(_ sender: Any) { } @objc func changeAvatar() { avatarImageView.image = UIImage(named: "avatar_1") } }
2.3. DisposeBag
Thứ quan trọng tiếp theo chính là Túi rác quốc dân. Đây là thứ mà bạn bắt buộc phải có trong class ViewController của bạn.
Vì nó giúp bạn quản lý các subscriptions và bộ nhớ tốt nhất & tự động.
Bạn tạo một property
mới cho RegisterViewController như sau:
private let bag = DisposeBag()
Tuỳ thuộc vào ViewController của bạn đóng vài trò như thế nào trong một flow
màn hình. Thì bạn sẽ có cách giải phóng các subscription
theo từng cách khác nhau. Còn với ví dụ này, khi nào ViewController giải phóng thì bag
sẽ giải phóng theo. Đi theo bag
là các subscription
của nó cũng bay hơi theo. EZ!
3. Observable Properties
Mình gọi chung là Observable
cho các properties
của ViewController. Nó có thể là:
- Observable
- Subject
- Traits
Về ví dụ demo, chúng ta sẽ tạo 1 property quản lý avatart
như sau:
private let image = BehaviorRelay<UIImage?>(value: nil)
Tại sao là Relay
, vì:
- Nó không bao giờ kết thúc
- Dữ liệu của nó sẽ liên quan tới dữ liệu của một UI Control trong View
Người tiền nhiệm của nó trong RxSwift trước đây là
Variable
.
Còn việc chọn BehaviorRelay
là vì mong muốn lúc nào cũng phải có giá trị khi có subscriber
đăng ký tới. Còn kiểu dữ liệu là UIImage?
để phù hợp với avatartImageView.image
.
Cách sử dụng nó thì như sau:
- Phát đi 1 dữ liệu
image.accpet(UIImage(...))
- Lấy giá trị hiện tại của nó
image.value
Ý nghĩa
Mình đã trình bày 1 lèo các bước để bạn thêm một thuộc tính cho ViewController với kiểu là một Observable. Tại sao phải thêm thuộc tính kiểu này?
Giải quyết bài toán Rx hay Reactive Programming thì bạn cần phải có nguồn phát (Observable). Đó sẽ là tâm điểm để phát sinh các sự kiện và kéo theo sự thay đổi về mặt giao diện của ViewController.
Bên cạnh đó, sử dụng các Relay
được xem là cách kết nối giữa code Rx & code non-Rx. Tức là, bạn không cần phải thay đổi quá nhiều code trong project hiện tại của bạn.
Số lượng các thuộc tính này được sử dụng thì tuỳ thuộc vào các use-case trong ViewController. Chứ không phải cứ 1 UI Control thì phải có 1 Observable đi kèm. Như vậy quá máy móc và đôi khi gây ra các hiệu ứng phụ mà mình không kiểm soát được hết các dòng dữ liệu.
4. Actions
Phần này, mình sẽ làm nhiều bạn thất vọng. Vì mình sẽ không dùng tới button.rx.tap
. Đó là cái tạo nên thương hiệu của RxSwift. Nhưng nhiều bạn lại hiểu sai, chỗ không gian .rx
ở đây chính là của RxCocoa. Qua phần 3 của series thì mình sẽ dùng tới không gian .rx
đó. Còn bây giờ, mọi thứ vẫn như lúc bạn mới học iOS.
Bắt các sự kiện người dùng sẽ thông qua các IBActions.
Quay về demo của mình nào. Sang phần Actions chính là xử lý các sự kiện mà người dùng tác động lên giao diện. Trong ví dụ, thì bạn mở function changeAvatar
và thêm phần xử lý này vào
@objc func changeAvatar() { let img = UIImage(named: "avatar_1") image.accept(img) }
(các name cho UIImage, thì bạn có thể sử dụng các image của riêng bạn nha.)
Giờ thử run project và xem kết quả nào.
Vẫn không có gì thay đổi hết. Tất nhiên là bạn chưa thực hiện bước sau cùng. Đó là lắng nghe tới những gì được phát ra.
5. Subscribe
Sau việc emit
dữ liệu, là việc subscribe
để nhận được dữ liệu. Việc subscribe với chúng ta đã làm nhiều rồi. Nhưng quan trọng, trong đó là phải cập nhật UI lại với dữ liệu mới. Quay về code demo, bạn truy cập tới viewDidLoad
của RegisterViewController và thêm code subscribe vào.
override func viewDidLoad() { super.viewDidLoad() title = "Register" configUI() //subscription image .subscribe(onNext: { img in self.avatarImageView.image = img }) .disposed(by: bag) }
Toàn là kiến thức cũ thôi. Vì BehaviorRelay
cũng là một Subject
, nên hoàn toàn có thể subscribe
nó. Bạn chỉ cần quan tâm tới .onNext
mà thôi. Các trường hợp error
hay completion
là không bao giờ xảy ra với Relay
.
Khi bạn quen với Reactive Programming rồi, việc thiết lập các
subscriber
này bạn sẽ làm lúc ban đầu luôn. Mọi thứ sẽ được khai báo (từ outlet tới action đều được Rx hoá ….) và chỉ cần tương tác 1 phát là kéo theo các sự thay đổi khác diễn ra. Quá trình này hoạt động tự động và độc lập với nhau.
Và khi bạn muốn BehaviorRelay
có thêm error
hay completed
thì có thể biến đổi một chút như sau:
image.asObservable() .subscribe(onNext: { img in self.avatarImageView.image = img }) .disposed(by: bag)
Toán tử asObservable()
sẽ xoá đi mọi khoảng cách để chúng ta thêm tự tin gần nhau hơn. Bạn hay run project và xem kết quả được như mình mong muốn hay không.
Ngoài ra, bạn có thể xử lý để áp dụng cho nhiều thay đổi lên nhiều UI Control trong ViewController với mỗi lần nhận được dữ liệu. Ví dụ như sau:
func updateUI() { // thay đổi Lable // thay đổi Button // update Tableview // ... }
Update lại chỗ subcriber
thì như sau:
images.asObservable() .subscribe(onNext: { [weak self] photos in self?.updateUI() }) .disposed(by: bag)
OKAY! Tới đây thì mình xin kết thúc bài viết đầu tiên và Hello thành công ViewController. 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.
Tạm kết
- Setup & Import RxSwift vào ViewController
- Thêm các thuộc tính cần thiết (Observable & DisposeBag)
- Phát dữ liệu đi thông qua các IBAction
- Update UI bằng subscribe tới các Observable
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)