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 13, 2020

RxSwift vs. UIKit – Working with Cache Data

RxSwift

Contents

  • Chuẩn bị
  • 1. Cache Data
  • 2. Save to Disk
    • 2.1. Mục đích
    • 2.2. Tạo path
    • 2.3. Lưu array items
  • 3. Read file
  • 4. Implement
  • Tạm kết

Chào bạn đến với Fx Studio. Tiếp tục cuộc hành trình của chúng ta trong thế giới RxSwift. Bài viết này về chủ đề Working with Cache Data. Đây cũng là phần mà các newbie iOS dev thường bỏ sót trong quá trình học. Bạn hãy xem đây là một bài phụ đạo để hoàn thiện hết kĩ năng của bản thân.

Bài viết này sẽ tiếp nối bài viết tương tác với API bằng RxSwift. Nếu bạn chưa biết về nó thì có thể tham khảo link sau:

    • RxSwift vs. UIKit – Fetching Data from API

Và mọi việc đã ổ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

Bạn cũng không cần phải thêm bất cứ màn hình hay file nào nữa. Mọi việc sẽ tiếp nối tại MusicListViewController.

1. Cache Data

Cache Data là thành phần không thể thiếu trong bất cứ ứng dụng mobile nào. Bạn có thể sử dụng dữ liệu từ API/Server và sau đó hiển thị lên giao diện. Nhưng vẫn còn rất nhiều trường hợp nữa mà bạn cần phải xử lý. Ví dụ như:

    • Không có kết nối mạng
    • Có nhiều màn hình sử dụng cùng 1 dữ liệu từ 1 url API/Server
    • Pre-load dữ liệu
    • Tạo trải nghiệm tốt nhất cho người dùng
    • Luôn hiển thị dữ liệu trước khi gọi API/Server
    • …

Và trong không gian RxSwift, chúng lại có thêm nhiều việc cần xử lý nữa … liên quan tới Observables hay update UI.

Về loại Cache Data, ta có rất nhiều lựa chọn cho bạn. Như:

    • Files
    • UserDefault
    • CoreData
    • Realm
    • Sqlite
    • *.plitst , *.json …
    • …

Nhưng về bản chất, chúng là nơi tập trung dữ liệu trên bộ nhớ của máy. Khi cần sử dụng thì chúng ta sẽ đọc từ đó lên và tiến hành cập nhật cho giao diện của ứng dụng. Muốn thêm hoặc update thì tiến hành lưu dữ liệu mới vào.

Về phân loại Cache Data, bạn có thể phân làm 2 loại chính như sau:

    • Data Manager : loại có sự quản lý tập trung. Thường sẽ là các framework bọc lại các kiểu Cache Data. Điểm hình như: Realm, CoreData …
    • Data Files : nó chỉ là file với kiểu định dạng phù hợp cho dữ liệu mà bạn muốn lưu trữ lại. Loại này, bạn cần phải xử lý việc đọc & ghi dữ liệu. Điển hình như: sqlite, *.plist, *.json …

Trong phạm vi của bài viết, chúng ta sẽ sử dụng *.json để làm Cache Data cho chương trình.

Khá là đơn giản, nhưng lại rất hiệu quả.

2. Save to Disk

2.1. Mục đích

Công việc đầu tiên chính là việc lưu dữ liệu. Trước khi vào phần code demo, bạn cần nắm được các công việc của bạn sẽ làm là như thế nào?

  • Khi bạn request API và nhận được dữ liệu mới nhất
  • Sau đó tiến hành update lên UI của ứng dụng
  • Bên cạnh đó sẽ lưu lại các dữ liệu đó vào bộ nhớ local trong máy

Để khi mở lại ứng dụng hay vào lại màn hình đó. Thường trước khi việc request API được thực thi, thì giao diện chúng ta là trắng xoá. Và lúc này, người dùng phải chờ cho tới lúc nhận được dữ liệu mới về.

Nếu trường hợp mạng chậm thì coi như tèo. Ahuhu!

Để đảm bảo tính liên tục trong ứng dụng thì trước khi request API, chúng ta sẽ lấy dữ liệu lưu mới nhất từ bộ đệm và hiển thị chúng lên giao diện. Sau đó, nếu nhận được dữ liệu nào từ response, thì UI sẽ cập nhật lại lần nữa.

Quay về ứng dụng demo, vì dữ liệu chúng ta vẫn còn đơn giản lắm. Nên sử dụng *.json để lưu trữ là OKE rồi. Các phần sau bạn sẽ học về RxRealm, khi đó tha hô mà sáng tạo.

2.2. Tạo path

Muốn lưu được dữ liệu thành file *.json, thì bạn phải biết được nơi nào sẽ lưu trữ file đó. Lựa chọn đơn giản sẽ là Documents của ứng dụng. Và để xác định nơi cần lưu thì bạn cần biết được đường dẫn tới (path) Documents. Bạn mở file MusicListViewController và thêm function sau:

    static func cachedFileURL(_ fileName: String) -> URL {
        return FileManager.default
            .urls(for: .cachesDirectory, in: .allDomainsMask)
            .first!
            .appendingPathComponent(fileName)
    }

Chỉ cần truyền cho nó một tên file thì bạn đã có full một đường dẫn xịn sò rồi. Phần này bạn tự tìm hiểu thêm nhé, Rx nó có quá nhiều extension mà chúng ta không biết. Chúng ta thêm một biến cho class nữa.

    private let musicsFileURL = cachedFileURL("musics.json")

Bạn chọn tên tuỳ ý nha. Chúng ta tiến sang function loadAPI() và tách phần handle subcribe ra riêng 1 function để dễ xử lý. Đặt tên là processMusics.

    private func processMusics(newMusics: [Music]) {
        // update UI
        DispatchQueue.main.async {
            self.musics = newMusics
            self.tableView.reloadData()
        }
        
        
    }

Edit lại một tí ở subscribe tới Observable của API.

            .subscribe(onNext: { musics in
                self.processMusics(newMusics: musics)
            })

2.3. Lưu array items

Để lưu Array Musics nhận được vào file, công việc của bạn là phải biến đổi kiểu dữ liệu cho phù hợp để lưu trữ.

Nếu bạn còn nhớ tới Codable thì trong đó còn 1 phần là Encoder. Nó ngược lại với Decoder . Và kiểu mong muốn biến đổi thành là JSON, nên sẽ dùng đối tượng JSONEncoder.

Ta thêm đoạn code này vào function processMusics.

        // save to file
        let encoder = JSONEncoder()
        if let musicsData = try? encoder.encode(newMusics) {
            try? musicsData.write(to: musicsFileURL, options: .atomicWrite)
        }

Trong đó:

  • encoder là đối tượng dùng để chuyển đổi dữ liệu thành JSON
  • Biến đổi Array Music thành Data với đối tượng là musicsData
  • .write dùng để lưu thành file với URL ở trên chúng ta đã tạo

Bạn xem lại function processMusics cho có cái nhìn tổng quát:

    private func processMusics(newMusics: [Music]) {
        // update UI
        DispatchQueue.main.async {
            self.musics.accept(newMusics)
            self.tableView.reloadData()
        }
        
        // save to file
        let encoder = JSONEncoder()
        if let musicsData = try? encoder.encode(newMusics) {
            try? musicsData.write(to: musicsFileURL, options: .atomicWrite)
        }
    }

Chúng ta đã xong phần lưu trữ dữ liệu. Tiến sang phần tiếp theo nào!

3. Read file

Đi kèm với việc lưu dữ liệu là việc đọc dữ liệu.

Việc này cũng giúp chúng ta kiểm tra thử lưu dữ liệu thành công hay không nữa à.

Bạn quay về viewDidLoad và thử load lại dữ liệu đã lưu xem như thế nào nha. À, để cho chắc ăn, bạn hãy build ứng dụng và chạy trước một lần. Tạm thời comment việc gọi API lại, thêm đoạn code đọc file vào

    override func viewDidLoad() {
        super.viewDidLoad()
        configUI()
        //loadAPI()
        
        // read file
        let decoder = JSONDecoder()
        if let musicsData = try? Data(contentsOf: musicsFileURL),
           let preMusics = try? decoder.decode([Music].self, from: musicsData) {
            self.musics = preMusics
        }
    }

Trong đó:

  • Lần này chúng ta biến đổi ngược lại từ Data thành Array Music
  • Data được đọc lên từ file với url đã cài đặt
  • preMusics là biến cho dữ liệu biến đổi bằng JSONDecoder, vì ta đã biết kiểu dữ liệu chính của nó là JSON.
  • Nếu mọi biến đều đúng, thì ta xét giá trị đó cho music và reload Tableview

Build lên và cảm nhận kết quả! Tiếc là phần này mình không show ảnh kết quả cho bạn được. Nếu như bạn đã gọi trước 1 lần API, sau đó comment đoạn code đó lại. Thì lần build tiếp theo sẽ hiển thị dữ liệu đã lưu lên TableView.

4. Implement

Sau khi đã cài đặt xong 2 công việc chính là lưu & đọc rồi. Thì bây giờ, còn việc sử dụng chúng nó sao cho hợp lý thôi. Bạn hãy nhớ lại bài Hello ViewController với việc sử dụng Subject cho thuộc tính của class (ViewController). Vì, nó theo nguyên tắc của Rx nói chung.

    • Chúng ta tách biệt các công việc (load API, save data & read data)
    • Không có trường hợp việc này xong thì gọi việc khác thức thi
    • Các công việc sẽ thực thi dựa vào việc dữ liệu được phát đi (emit)
    • Mọi thứ sẽ được tiến hành khai báo ngay từ đầu và hoạt động một cách tự động

Tất cả cái trên dẫn tới một điều là chúng ta sẽ tạo một Subject để vừa lưu dữ liệu cho Array Music và vừa dùng làm nguồn phát.

    private var musics = BehaviorRelay<[Music]>(value: [])

Vẫn là BehaviorRelay là lựa chọn hàng đầu ngay lúc này. Chúng ta sẽ update vài chỗ sau:

  • Phát dữ liệu đọc được từ Cache Data lên
        let decoder = JSONDecoder()
        if let musicsData = try? Data(contentsOf: musicsFileURL),
           let preMusics = try? decoder.decode([Music].self, from: musicsData) {
            self.musics.accept(preMusics)
        }
  • Phát dữ liệu mới nhận được từ API
    private func processMusics(newMusics: [Music]) {
        // update UI
        DispatchQueue.main.async {
            self.musics.accept(newMusics)
            self.tableView.reloadData()
        }
        
        // save to file
        let encoder = JSONEncoder()
        if let musicsData = try? encoder.encode(newMusics) {
            try? musicsData.write(to: musicsFileURL, options: .atomicWrite)
        }
    }
  • Sử dụng trong TableView
extension MusicListViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        musics.value.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MusicCell
        
        let item = musics.value[indexPath.row]
        cell.nameLabel.text = item.name
        cell.artistNameLabel.text = item.artistName
        cell.thumbnailImageView.kf.setImage(with: URL(string: item.artworkUrl100)!)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

 

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

  • Cách lấy đường dẫn tới file lưu ở Documents bằng Rx
  • Lưu dữ liệu & đọc dữ liệu theo định dạng JSON đơn giản
  • Sử dụng Subject để phát dữ liệu từ API và từ Cache

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

FacebookTweetPinYummlyLinkedInPrintEmailShares6

Related Posts:

  • Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13
    Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13
  • RxSwift vs. UIKit – Networking
    RxSwift vs. UIKit – Networking
  • RxCocoa Basic – Extend UIKit
    RxCocoa Basic – Extend UIKit
  • RxSwift – Phần 2 : UIKit
    RxSwift – Phần 2 : UIKit
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:

  • SwiftUI - Phần 3 : Tích hợp SwiftUI và UIKit
    SwiftUI - Phần 3 : Tích hợp SwiftUI và UIKit
  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
  • SwiftUI - Phần 8 : Working with List
    SwiftUI - Phần 8 : Working with List
  • RxCocoa Basic - Working with multi UI Control
    RxCocoa Basic - Working with multi UI Control
  • RxSwift – Phần 2 : UIKit
    RxSwift – Phần 2 : UIKit

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!