Contents
Chào bạn đến với Fx Studio. Hành trình trong thế giới RxSwift của chúng ta vẫn còn dài. Bây giờ, chúng ta tới phần sử dụng Model trong RxSwift vs. UIKit. Điểm đặc biệt là sẽ Tạo Model với Custom Observable. Ngoài ra, sẽ giải quyết thêm bài toán call back khi sử dụng Model. Từ đó, tạo tiền đề cho xử lý bất đồng bộ sau này.
Để đảm bảo kiến thức của bài viết được truyền tải đầy đủ tới bạn. Thì cần tham khảo lại các link sau đây:
Nếu mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 11
- Swift 5.x
Chúng ta vẫn sử dụng project ở bài trước. Ngoài ra, dành cho các bạn chưa biết checkout ở đâu, thì hãy tham khảo link sau:
-
- Link: checkout
- Thư mục:
/Examples/BasicRxSwift
1. Create Model
Mục tiêu chính là phần này bạn sẽ tập viết Model với việc áp dụng RxSwift vào. Nó sẽ có một số chút khác lạ so với cách viết truyền thống. Và vấn đề nữa chúng ta cần quan tâm nhiều nhất là:
Call back
Để bắt đầu, chúng ta sẽ vào phần đầu tiên là tạo các file code cho Mode. Và define những thứ cần thiết.
1.1. Create
Phần này chúng ta không có thêm bất cứ ViewController nào hết. Thay vì đó chúng ta có 1 file là RegisterModel
. File này giả định là sẽ dùng tương tác với API để tiến hành đăng kí 1 account cho người dùng.
Tất cả đều là giả thôi nha!
Ta có code ban đầu của RegisterModel như sau:
import Foundation import UIKit final class RegisterModel { //singleton private static var sharedRegisterModel: RegisterModel = { let sharedRegisterModel = RegisterModel() return sharedRegisterModel }() class func shared() -> RegisterModel { return sharedRegisterModel } // init private init() {} func register(username: String?, password: String?, email: String?, avatar: UIImage?) { } }
Trong class RegisterModel có:
- 1 singleton để tiện tay mà gọi cho nhanh. Không cần tạo mới nhiều đối tượng cho nó phức tạp.
- 1 function chính là
register
với 4 tham số. - Các tham số này nhận dữ liệu trực tiếp từ các View, nên chúng nó sẽ là các
optional
.
Tiếp theo, ta sẽ Rx hoá class này bằng việc import:
import RxSwift
Và biến đổi lại function register
thành như sau:
func register(username: String?, password: String?, email: String?, avatar: UIImage?) -> Observable<Bool> { }
Bạn sẽ thấy chúng ta sẽ return về 1 Observable
. Và kiểu dữ liệu Output là Bool
với
Bool
cho việc thành công hay thất bại.
1.2. Define Error
Hiển nhiên, chúng ta cũng phải quan tâm tới việc thất bại khi thực hiện tương tác với Model. Để giúp cho việc quản lý Error tốt nhất, chúng ta phải define Error riêng ra. Bạn thêm đoạn code này vào.
enum APIError: Error { case error(String) case errorURL var localizedDescription: String { switch self { case .error(let string): return string case .errorURL: return "URL String is error." } } }
Đặt ở đâu cũng được hoặc bạn có thể tạo riêng 1 file swift cho nó cũng không sao. Trong struct trên, thì tạm thời khai báo 2 trường hợp lỗi:
error(String)
cho các trường hợp lỗi chung chung. Cần một lời mô tả lỗi gì đó bằng String.errorURL
dùng trong trường hợp URL bị lỗi.localizedDescription
để mô tả cụ thể lỗi đó là gì. Dùng trong việc hiển thị lên View.
Cuối cùng, bạn có thể thêm các trường hợp lỗi của riêng bạn. Như username
quá dài, password
rỗng … Tự do sáng tạo nha!
1.3. Custom Observable
Cuối cùng, cũng tới phần việc quan trọng nhất. Trước tiên, để cho Xcode không báo lỗi, thì tạo trước một Custom Observable trong function register
.
func register(username: String?, password: String?, email: String?, avatar: UIImage?) -> Observable<Bool> { return Observable.create { observer in return Disposables.create() } }
Như đã được trình bày ở các bài trước. Với toán tử .create
chúng ta cần 1 một closure để handle cho việc tạo Custom Observable. Khi tạo ra 1 Observable, đối tượng observer
sẽ quyết định các logic & hành vi của Observable. Rồi từ đó phát các dữ liệu liên quan. Cuối cùng phải có 1 Disposable
để nó có thể ném vào được túi rác nào đó.
Phần tiếp theo, bạn cần xử lý logic trong create
. Vì đây là đặc điểm riêng của từng class & của riêng từng project, nên logic sẽ khác nhau. Còn trong demo ví dụ này thì sẽ như sau:
- Kiểm tra các tham số đầu vào của function
register
. Nếu chúng lỗi thì phảiemit
lỗi trở về. Phân biệt từng loại lỗi cho từng tham số truyền vào. - Khi các tham số đúng với điều kiện. Giả sử chúng ta gọi API và thực hiện sau 2 giây sau thì
emit
đi dữ liệu. Mô tả cho việc gọi API thành công.
EZ GAME!
1.4. Handle Logic
Error cũng đã được define rồi. Và nghe qua logic trên thì cũng đơn giản. Ít hôm tới bài RxSwift với API, thì chúng ta sẽ đau não sau. Giờ tạm thời code sẽ như thế này.
func register(username: String?, password: String?, email: String?, avatar: UIImage?) -> Observable<Bool> { return Observable.create { observer in // check params // username if let username = username { if username == "" { observer.onError(APIError.error("username is empty")) } } else { observer.onError(APIError.error("username is nil")) } // password if let password = password { if password == "" { observer.onError(APIError.error("password is empty")) } } else { observer.onError(APIError.error("password is nil")) } // email if let email = email { if email == "" { observer.onError(APIError.error("email is empty")) } } else { observer.onError(APIError.error("email is nil")) } // avatar if avatar == nil { observer.onError(APIError.error("avatar is empty")) } DispatchQueue.main.asyncAfter(deadline: .now() + 2) { observer.onNext((true)) observer.onCompleted() } return Disposables.create() } }
Cũng không quá nhiều thứ khó hiểu trong function trên. Giờ bạn cần chú ý vào:
- Trường hợp muốn phát đi
.error
observer.onError(APIError.error("username is empty"))
Sử dụng hàm .onError
và ném vào đó 1 đối tượng Error theo ý mình.
- Trường hợp muốn phát đi
.onNext
observer.onNext(true)
Vì chúng ta chỉ muốn báo là thành công hay không, nên chỉ dùng 1 biến Bool đơn giản.
Vì khi phát đi error
, sequence observable sẽ kết thúc. Nên nếu bạn chỉ có phát đi .onNext
một lần thôi, thì phải thêm đoạn completed
cho sequence observable kết thúc. Tránh hậu hoạ về sau.
observer.onCompleted()
Vậy là tạm thời xong phần Model!
2. Subscribe Model
Giờ là cách sử dụng function vừa được tạo ra. Bạn về lại file RegisterViewController. tại hàm register
chúng ta tiến hành subscribe
tới function vừa viết ở trên.
@IBAction func register(_ sender: Any) { RegisterModel.shared().register(username: usernameTextField.text, password: passwordTextField.text, email: emailTextField.text, avatar: avatarImageView.image) .subscribe(onNext: { done in print("Register successfully") }, onError: { error in if let myError = error as? APIError { print("Register with error: \(myError.localizedDescription)") } }, onCompleted: { print("Register completed") }) .disposed(by: bag) }
Trong đó
.onNext
handle dữ liệu nhận được..onError
để handle lỗi nhận được..onCompleted
dùng handle khi Observable kết thúc.
Đây cũng chính là cách
call back
khi tương tác bất đồng bộ.
Thay vì sử dụng closure và gởi về một đối tượng Result, với 2 case
thành công hay thất bại. Giờ chúng ta thực hiện call back
đơn giản bằng việc nhận dữ liệu phát ra từ 1 Observable thôi.
Lưu ý:
- Luôn ghi nhớ việc lưu lại
subscription
tại disposeBag. - Ngoài ra, đoạn code trên chỉ hiển thị
log
ở console mà thôi. Trong thực tế thì bạn sẽ handle thêm về UI hay chuyển màn hình tại đó.
Bạn hãy run lại project và test lại xem hoạt động đúng như mình mong muốn hay không.
3. Using Traits
Bạn cũng thấy RegisterModel, thì function register
chỉ có phát đi một lần và kết thúc. Cho dù là error
đi nữa. Và trong thực tế cuộc sống cũng như vậy, khi đó thì không cần thiết phải dùng đầy đủ các tính năng của 1 Observable.
Hãy nhớ tới Trait
Chúng ta có 3 Trait hay dùng trong RxSwift
-
- Single
- MayBe
- Completable
3.1. Single
Nếu bạn quên cách tạo & sử dụng Trait, thì có thể xem lại ở bài Traits. Còn với ví dụ demo này, bạn có thể viết 1 function register
mới với tham số trả về là Single
.
Mình sẽ clone lại function trên và đặt tên rất hay như sau register2
. Bạn tham khảo đoạn code này:
func register2(username: String?, password: String?, email: String?, avatar: UIImage?) -> Single<Bool> { return Single.create { single in // check params // username if let username = username { if username == "" { single(.error(APIError.error("username is empty"))) } } else { single(.error(APIError.error("username is nil"))) } // password if let password = password { if password == "" { single(.error(APIError.error("password is empty"))) } } else { single(.error(APIError.error("password is nil"))) } // email if let email = email { if email == "" { single(.error(APIError.error("email is empty"))) } } else { single(.error(APIError.error("email is nil"))) } // avatar if avatar == nil { single(.error(APIError.error("avatar is empty"))) } DispatchQueue.main.asyncAfter(deadline: .now() + 2) { single(.success(true)) } return Disposables.create() } }
Với Single
, bạn cần quan tâm tới 2 trạng thái:
success
là thành côngerror
là thất bại
Và bạn cũng không cần quan tâm phải kết thúc sequence observable của mình nữa. Nhiều trường hợp lỗi vì bất cẩn cũng. Mà theo cách dùng như thế, sẽ được giảm đi rất nhiều lỗi kiểu như vậy.
3.2. Subscription
Tiếp tới là phần sử dụng. Cũng như như subscribe tới 1 Observable, tuy nhiên với Single lại đơn giản hơn nhiều.
Cách 1 : dùng nguyên Single
RegisterModel.shared().register2(username: usernameTextField.text, password: passwordTextField.text, email: emailTextField.text, avatar: avatarImageView.image) .subscribe(onSuccess: { done in print("Register successfully") }, onError: { error in if let myError = error as? APIError { print("Register with error: \(myError.localizedDescription)") } }).disposed(by: bag)
Tương tự như bên là cách tạo ra Single . Thì bên subscribe tới nó, cũng chỉ quan tâm tới 2 trạng thái:
onSuccess
handle thành côngonError
handle thất bại
Cách 2: biến đổi về lại Observable
Bạn muốn biến Single trở lại Observable, thì chỉ cần gọi asObservable()
thôi. Xem ví dụ luôn cho nóng.
RegisterModel.shared().register2(username: usernameTextField.text, password: passwordTextField.text, email: emailTextField.text, avatar: avatarImageView.image) .asObservable() .subscribe(onNext: { done in print("Register successfully") }, onError: { error in if let myError = error as? APIError { print("Register with error: \(myError.localizedDescription)") } }, onCompleted: { print("Register completed") }) .disposed(by: bag)
OKAY! Tới đây là kết thúc bài viết này. Tuỳ thuộc vào yêu cầu mà bạn hãy sử dụng chúng nó một cách hợp lý nha! 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
Tất cả ở trên cũng chỉ là 1 phương pháp call back
về cho nó xịn sò hơn thôi. Bạn thử xem dòng code sau:
func register( ..... , completion: Result<Bool, Error?> -> Void) { // code đây nè! }
Thì bạn hãy chú ý tới tham số completion
đó. Bạn sẽ nhận thấy …
… dăm ba cái đồ RxSwift thôi mà!
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)