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 September 6, 2020

RxSwift – DisposeBag

RxSwift

Contents

  • Chuẩn bị
  • 1.DisposeBag và vấn đề
    • 1.1. Vấn đề
    • 1.2. DisposeBag
  • 2. Dispose Subscription
  • 3. DisposeBag
  • 4. Dispose vs. DisposeBag
    • 4.1. Sử dụng Dispose
    • 4.2. Sử dụng DisposeBag
    • 4.3. Retain cycle
    • 4.4. weak self
  • 5. Disposable
    • 5.1. Create
    • 5.2 Disposable
    • 5.3. Subscribe
    • 5.4. onError
    • 5.5. non terminate
  • Tạm kết

Chào bạn đến với Fx Studio. Bài viết này với chủ đề là DisposeBag, là phần tiếp nối trong series RxSwift.

Chúng ta đã lang thang qua được phần Observables và đây cũng là phần tiếp sau nó. Và nếu bạn chưa biết về Observables thì có thể ghé sang link này để đọc lại.

    • RxSwift – Observables

Còn mọi thứ đã ổn rồi thì …

Bắt đầu thôi!

Chuẩn bị

  • Xcode 11
  • Swift 5
  • Playground

Cũng không phải chuẩn gì nhiều ở đây cả. Vẫn là project lần trước và bạn tạo thêm một file Playground là ổn thôi. Nếu bạn quên link checkout source code thì có thể checkout tại đây.

1.DisposeBag và vấn đề

Điểm nguy hiểm nhất khi sử dụng RxSwift (hay Reactive Programming) là việc quản lý bộ nhớ. Nó không hoàn toàn tự động và  không được tối ưu. Khi có quá nhiều nguồn phát và quá nhiều đối tượng lắng nghe tới các nguồn phát đó, thì bạn có dám chắc rằng bạn đã giải phóng tất cả các đối tượng hay không.

Một đối tượng Observable sẽ phát đi dữ liệu khi nó được một đối tượng khác (hay cái gì đó) đăng ký tới.

1.1. Vấn đề

Bạn chỉ cần tò mò một chút thì sẽ có nhiều vấn đề trong cơ chế hoạt động trên. Như:

  • Nếu Observable không kết thúc thì sao nào?
  • Không muốn nhận nữa mà Observable vẫn cứ phát dữ liệu đi?
  • Chã lẻ lúc nào cũng phải mang theo 1 closure để đi subscribe Observable.
  • Có nên huỷ nó hay không huỷ nó, khi phải làm việc với bất đồng bộ. Như đang chờ dữ liệu từ API trả về
  • Lỡ quên huỷ mất tiêu … thì có toang không ta?
  • …

Quá nhiều, chắc cũng phải tới 1001 lý do ở đây rồi. Nhưng ta chỉ cần quan tâm là mối liên kết giữa chúng không bị giải phóng. Dù ít hay nhiều thì bộ nhớ của thiết bị sẽ bị chiếm dụng. Nguy hiểm hơn là chúng ta sẽ không handle được sự thay đổi tới các thành phần khác mà cùng đăng ký tới Observable.

Với Mobile cho dù ram chip mạnh tới đâu đi nữa. Thì đó, vẫn là các thiết bị bị giới hạn bộ nhớ và tài nguyên. Nên việc lập trình với nó cần phải coi trong việc quản lý bộ nhớ.

1.2. DisposeBag

DisposeBag, Dispose & Disposable thì chỉ có trong nền tảng RxSwift và nó không tương đồng với các nền tảng khác trong đa vũ trụ Rx. Đây là cơ chế giúp cho RxSwift có thể quản lý bộ nhớ của mình được tốt nhất.

Đây cũng là cách nhanh nhất đơn phương kết thúc một đăng ký (subscription) từ phía người đăng ký (subscriber). Mà không cần đợi nguồn phát phát đi error hay completed.

Để hiển cách hoạt động của nó, bạn tiếp tục theo dõi các phần sau.

2. Dispose Subscription

Khi một Observable được tạo ra, nó sẽ không hoạt động hay hành động gì cho tới khi có 1 subscriber đăng ký tới. Việc subscriber khi đăng ký tới thì gọi là subscription. Lúc đó, sẽ kích hoạt Observable (hay gọi là trigger) bắn đi các giá trị của mình. Việc này cứ lặp đi lặp lại, cho đến khi phát ra .error hoặc .completed.

Vấn đề chính bắt đầu từ đây. Bạn không bao giờ biết lúc nào nó kết thúc hoặc bạn phó mặc nó với số phận. Khi bạn chấp nhận buông tay thì hậu quả để lại là các đối tượng trong chương trình của bạn không bao giờ bị giải phóng. Và bạn không biết khi nào dữ liệu tới hoặc de-bugs để biết lỗi từ đâu mà ra.

let observable = Observable<String>.of("A", "B", "C", "D", "E", "F")
    
let subscription = observable.subscribe { event in
    print(event)
}

Ta có một đoạn code với việc khai báo 1 Observable với kiểu dữ liệu Output là String. Tiếp sau đó, tạo thêm 1 subscription bằng việc sử dụng toán tử .subscribe cho Observable. Cung cấp thêm 1 closure để handle các dữ liệu nhận được.

Tiếp theo, để dừng việc phát của Observable thì bạn sử dụng hàm .dispose()

subscription.dispose()

Lúc này, kết nối của subscription sẽ chấm dứt và Observable sẽ dừng phát.  Và hoà bình sẽ trở lại với thế giới!

3. DisposeBag

Câu chuyện vẫn còn vui, bạn xem tiếp ví dụ code sau:

Observable<String>.of("A", "B", "C", "D", "E", "F")
        .subscribe { event in
            print(event)
        }

Nếu như bạn tạo ra 1 subscription để quản lý các đăng ký. Thì mọi thứ đơn giản rồi. Tuy nhiên vẫn là câu nói cũ Đời không như là mơ. Thường trong code Rx thì:

  • Các đối tượng subscriber hầu như không tồn tại hoặc không tạo ra.
  • Các subscription sẽ được tạo ra nhằm giữ kết nối. Nó sẽ phục vụ cho tới khi nào class chứa nó bị giải phóng.
  • Có quá nhiều subscription trong một class.
  • Nhiều trường hợp muốn subscribe nhanh tới 1 Observable nên các subscription sẽ không tạo ra.

Không giải quyết riêng lẻ được thì gom lại mà xử lý thằng cầm đầu.

Vậy là, vấn đề bộ nhớ vẫn nhức nhói. Do đó, người ta sinh ra khái niệm mới DisposeBag (túi rác quốc dân). Bạn xem tiếp code ví dụ trên với việc thêm DisposeBag vào.

    let bag = DisposeBag()
    
    Observable<String>.of("A", "B", "C", "D", "E", "F")
        .subscribe { event in
            print(event)
        }
        .disposed(by: bag)

Lúc này, bạn không cần bận tâm lắm về Observable mình tự do làm càng. Tất cả sẽ được disposeBag quản lý và thủ tiêu.

Áp dụng vào trong project, khi bạn khai báo 1 disposeBag là biến toàn cục. Khi đó, bạn chỉ cần ném tất cả các subscription hoặc các Observable vào đó. Và yên tâm về vấn đề bộ nhớ sẽ không bị ảnh hưởng.

Tất nhiên, nếu bạn quên việc thêm dispose() hay disposeBag thì trình biên dịch sẽ nhắn lời yêu thương cho bạn biết.

4. Dispose vs. DisposeBag

Hai thằng Dispose và DisposeBag có gì khác nhau. Vì tất cả cũng đều phục vụ cho việc giải phóng subscription. Để hiểu hơn thì bạn phải đưa vào bài toán thực tế.

Ví dụ, ta có 1 ViewController với nhiều subscription. Công việc giờ quản lý và tránh đi retain-cycle khi các biến trong class ViewController không được tự động giải phóng.

4.1. Sử dụng Dispose

  • Với 1 subscription đơn lẻ
final class ViewController: UIViewController {
    var subscription: Disposable?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        subscription = theObservable().subscribe(onNext: {
            // handle subscription
        })
    }
    
    deinit {
        subscription?.dispose()
    }
}
  • Với nhiều subscription
final class ViewController: UIViewController {
    var subscriptions = [Disposable]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        subscriptions.append(theObservable().subscribe(onNext: {
            // handle subscription
        }))
    }
    
    deinit {
        subscriptions.forEach { $0.dispose() }
    }
}

Bạn hãy chú ý tới function deinit. Nó là nơi các subscription gọi việc tự huỷ .dispose().

4.2. Sử dụng DisposeBag

final class ViewController: UIViewController {
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        theObservable().subscribe(onNext: {
                // handle subscription
        })
        .disposed(by: disposeBag)
    }
}

Vậy thì function deinit đã đi đâu?

Một điều tuyệt vời của DisposeBag, là nó sẽ chịu trách nhiệm cho việc gọi dispose trên mỗi Disposable bên trong nó. Việc này sẽ được gọi khi bản thân DisposeBag bị deinit.

4.3. Retain cycle

Khi bạn đã cài đặt đầy đủ DisposeBag rồi, nhưng nguy cơ retain-cycle vẫn còn. Nhất là với với thao tác bất đồng bộ.

Ví dụ, bạn gọi kết nối tới 1 API. Lúc này, bạn phải chờ response trả về. Nhưng thời gian chờ quá lâu, bạn thoát khỏi màn hình đó. Và sau đó, response về. Đây là lúc nhiều câu chuyện bắt đầu

  • Về mặt quản lý biến và đối tượng thì chúng bằng nil khi chúng ta thoát khỏi màn hình đó.
  • Tuy nhiên vì để chờ response thì đâu đó, ta cần phải sử dụng chính đối tượng self để xử lý & update UI. Việc này tạo ra sự tham chiếu tới self.

Mà cái closure handle response đó lại được lưu trữ trong 1 Subscription. Và subscription đó lại ở trong 1 DisposeBag. DisposeBag lại ở trong ViewController.

Khi thoát khỏi màn hình,  hàm deinit sẽ chạy, nhưng chúng sẽ không deinit được vì trong closure trên vẫn còn được trỏ tới.

Mô tả code như sau:

final class MyViewController: UIViewController {
    private let disposeBag = DisposeBag()
    private let parser = MyModelParser()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let parsedObject = theObservable
            .map { json in
                return self.parser.parse(json)
            }
            
        parsedObject.subscribe(onNext:{ _ in 
            //do something
        })
        .disposed(by: disposeBag)
    }
}

4.4. weak self

Cách để giải quyết nó là bạn hãy nhớ về [weak self]. Bạn hãy xem đoạn code

let parsedObject = theObservable
    .map { [weak self] json in
        return self?.parser.parse(json) //compile-time error. What should be returned if `self` is nil?
    }

Tuy nhiên, nếu sử dụng [weak self], ta cần báo cho compiler phải trả về giá trị gì nếu self bị nil. Trong trường hợp này, cách tốt nhất là truyền parser trong capture list thay vì dùng self:

let parsedObject = theObservable
    .map { [parser] json in
        return parser.parse(json)
    }

Swift cho phép ta truyền một biến vào capture list mà không cần bất cứ thuộc tính nào như weak hoặc unowned. Nếu làm như thế, compiler sẽ biết phải giữ reference cho duy nhất parse (với strong reference), chứ không phải cho self.

Một cách nữa là sử dụng [unowned self] thay cho [weak self].

5. Disposable

5.1. Create

Trước tiên, bạn cần tìm hiểu về toán tử create. Nó sẽ giúp cho bạn tạo được một Observable. Ví dụ được thực hiện với kiểu Output là String.

Observable<String>.create(subscribe: (AnyObserver<String>) -> Disposable##(AnyObserver<String>) -> Disposable>)

Tham số cần truyền vào chính là 1 observer. Tới đây nhiều bạn sẽ loạn não 1 chút, mình xin tóm gọn lại thế này:

  • Observable là nguồn phát
  • Observer là nguồn nhận

Công việc của nó chính là:

  • Thiết lập đăng kí từ Observable tới Subscriber
  • Xác định các dữ liệu được phát ra bởi Observable tới Subscriber

5.2 Disposable

Disposable là một protocol với một phương thức dispose()

public protocol Disposable {
    func dispose()
}

Khi subscribe một Observable, Disposable giữ một tham chiếu đến Observable và Observable này giữ strong reference đến Disposable (Rx tạo ra một loại retain cycle). Nhờ vậy, khi user quay lại màn hình trước đó, Observable sẽ không bị giải phóng cho đến khi ta muốn giải phóng nó.

Lý thuyết thì nó như vậy, giờ ta tiến hành implement đầy đủ cho Observable nào:

Observable<String>.create { observer -> Disposable in
        observer.onNext("1")
        
        observer.onNext("2")
        
        observer.onNext("3")
        
        observer.onNext("4")
        
        observer.onNext("5")
        
        observer.onCompleted()
        
        observer.onNext("6")
        
        return Disposables.create()
    }

Bạn sẽ thấy trong closure đó, chúng ta tuỳ ý phát ra các dữ liệu. Quan trọng là return về Disposables , để có thể kết thúc việc đăng ký từ bên ngoài. Và nó cũng là đại diện cho mỗi lần đăng ký tới Observable.

5.3. Subscribe

Bạn khai báo thêm 1 túi rác quốc dân nữa.

let bag = DisposeBag()

Tiếp theo là .subscribe tới Observable nào

.subscribe(
        onNext: { print($0) },
        onError: { print($0) },
        onCompleted: { print("Completed") },
        onDisposed: { print("Disposed") }
        )
    .disposed(by: bag)

Handle full các sự kiện với các dữ liệu mà Observable có thể phát ra. Các hàm handle là onNext, onError, onCompleted và onDisposed sẽ tương ứng với từng kiểu dữ liệu mà Observable phát ra. Kết quả khi thực thi như sau:

1
2
3
4
5
Completed
Disposed

Bạn để ý kĩ thì sẽ không thấy 6. Đơn giản vì nó được phát ra sau .completed. Nên sẽ không bao giờ phát đi được và cũng không bao giờ nhận được. Áp dụng tương tự cho error.

5.4. onError

Khai báo 1 Error cho dễ quản lý

enum MyError: Error {
  case anError
}

Và phát error như sau:

observer.onError(MyError.anError)

Kết quả thực thi như sau với việc phát ra error sau khi phát ra 4

1
2
3
4
anError
Disposed

À, bạn tuỳ ý custom về phát Error rồi nha.

5.5. non terminate

Để nhìn rõ hơn việc DisposeBag ảnh hưởng như thế nào, chúng ta sẽ comment lại các lệnh sau:

  • disposed(by: bag)
  • observer.onError(MyError.anError)
  • observer.onCompleted()
disposed(by: bag)
observer.onError(MyError.anError)
observer.onCompleted()
enum MyError: Error {
      case anError
    }
    
    let bag = DisposeBag()
    
    Observable<String>.create { observer -> Disposable in
        observer.onNext("1")
        
        observer.onNext("2")
        
        observer.onNext("3")
        
        observer.onNext("4")
        
        //observer.onError(MyError.anError)
        
        observer.onNext("5")
        
        //observer.onCompleted()
        
        observer.onNext("6")
        
        return Disposables.create()
    }
    .subscribe(
        onNext: { print($0) },
        onError: { print($0) },
        onCompleted: { print("Completed") },
        onDisposed: { print("Disposed") }
        )
    //.disposed(by: bag)

Bạn xem kết quả như sau:

1
2
3
4
5
6

Như vậy, nếu không có disposeBag và không có việc phát ra error hay completed, thì subscribe sẽ vẫn đứng đó chờ Observable. Và subscription sẽ không kết thúc được.

OKAY! Mình xin kết thúc bài viết này tại đây. 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

  • Huỷ một subscription bằng dispose()
  • Quản lý nhiều subscription cho nó tự huỷ bằng DisposeBag
  • .create tạo ra một Observable và cài đặt các hành vi cho nó.

 

Okay! Tới đây, mình xin kết thúc bài viết về DisposeBag trong RxSwift. 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.

  • Bài viết tiếp theo tại đây.
  • Tham khảo : http://adamborek.com/memory-managment-rxswift/

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares42

Related Posts:

  • RxSwift - Phần 1 : Cơ bản
    RxSwift - Phần 1 : Cơ bản
  • RxSwift - Creating an Observable
    RxSwift - Creating an Observable
  • RxSwift - Transforming Operators
    RxSwift - Transforming Operators
  • RxSwift - Replay Subjects
    RxSwift - Replay Subjects
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 - Combining Operators
    RxSwift - Combining Operators
  • RxSwift - Tìm hiểu Operators & Hello world!
    RxSwift - Tìm hiểu Operators & Hello world!
  • RxSwift - Transforming Operators
    RxSwift - Transforming Operators
  • RxSwift - Replay Subjects
    RxSwift - Replay Subjects
  • RxSwift - Relays
    RxSwift - Relays

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!