Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on October 7, 2020

RxSwift vs. UIKit – Tạo Model với Custom Observable

RxSwift

Contents

  • Chuẩn bị
  • 1. Create Model
    • 1.1.  Create
    • 1.2. Define Error
    • 1.3. Custom Observable
    • 1.4. Handle Logic
  • 2. Subscribe Model
  • 3. Using Traits
    • 3.1. Single
    • 3.2. Subscription
  • Tạm kết

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:

    • Creating an Observable
    • Traits
    • Hello ViewController

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ải emit 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ông
  • error 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ông
  • onError 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!

FacebookTweetPinYummlyLinkedInPrintEmailShares16

Related Posts:

  • RxSwift vs. UIKit – Tương tác giữa các ViewController
    RxSwift vs. UIKit – Tương tác giữa các ViewController
  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
  • RxSwift vs. UIKit – Networking
    RxSwift vs. UIKit – Networking
  • RxSwift - Creating an Observable
    RxSwift - Creating an Observable
Tags: rxswift
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

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • RxSwift – Phần 2 : UIKit
    RxSwift – Phần 2 : UIKit
  • RxSwift vs. UIKit - Fetching Data from API
    RxSwift vs. UIKit - Fetching Data from API
  • Sử dụng Custom UIView vào SwiftUI Project - SwiftUI Notes…
    Sử dụng Custom UIView vào SwiftUI Project - SwiftUI Notes #16
  • RxSwift vs. UIKit – Networking
    RxSwift vs. UIKit – Networking
  • Storyboard & Tạo giao diện cơ bản trong iOS
    Storyboard & Tạo giao diện cơ bản trong iOS

Archives

  • 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)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!