Contents
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:
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!
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)