MVVM Swift

Hello mọi người! Trong bài hôm nay chúng ta sẽ phát triển ứng dụng lên một tầm cao mới. Đó là có thể sử dụng ứng dụng offline được. Không cần mạng internet, chúng ta vẫn có thể truy cập dữ liệu đã tải trước đó. Như các bạn thấy, một số ứng dụng tiêu biểu cho việc này là facebook. Khi bạn tắt mạng đi, thì nó vẫn có thể đăng bài, tải các bài trên feed đã lưu trước đó. Trông cũng khá ổn, nó tăng tính trải nghiệm người dùng hơn, chuyên nghiệp hơn đúng không nào? 😚

Vậy thì, hôm nay chúng ta làm điều đó, bằng 1 framework cho database, khá chuyên nghiệp đó là Realm. Vậy realm là gì? Có thể bằng vài câu thì không nói hết được các tiện ích mà framework này mang lại. Tuy nhiên, bạn có thể hiểu nôm na là realm giúp bạn lưu trữ giữ liệu, lôi ra khi cần. Bạn có thể thêm sửa xóa, có thể làm mọi thứ với dữ liệu bạn đã lưu. Rất tiện ích. Để hiểu hơn hãy đọc cẩn thận tài liệu của realm cho ios ở đây:

https://docs.mongodb.com/realm/sdk/ios/install/

Trong bài trước, chúng ta đã thêm loading cho phần tìm kiếm để nó chuyên nghiệp hơn. Vẫn còn vài bug nhỏ, nhưng các bạn tự fix xem nhé. Trong bài lần này, tôi sẽ thêm realm vào để lưu trữ toàn bộ kết quả mà bạn đã search vào bộ nhớ. Sau đó mỗi lần search, nếu như nó tìm thấy kết quả đã có trong bộ nhớ, nó sẽ tiến hành lấy kết quả đó và hiển thị vào table, thay vì request lên network.

Đầu tiên như những bài trước, bạn phải tải code tại đây:

https://github.com/codetoanbug/MVVMSample.git

Và chuyển qua branch bai4 các bạn nhé. Cách chuyển thì xem lại bài đầu tiên nha.

Rồi bây giờ cùng bắt đầu làm quen và sử dụng realm bằng 1 cách chuyên nghiệp nào.

  1. Xây dựng realm manager
Lớp RealmManager

Hãy xem xét lớp RealmManager mà tôi đã tạo. Source code hơi dài, nhưng đừng lo lắng, tôi sẽ phân tích từ từ từng dòng 1.

Ý tưởng của lớp này như sau:

  • Tôi muốn tạo 1 lớp quản lý database có đầy đủ tính năng thêm(save), sửa(update), xóa(delete).
  • Tôi muốn lưu trữ các model kế thừa từ Codable. Bằng việc này tôi có thể lưu thẳng kết quả trả về của API từ server vào realm.
  • Mỗi model phải có 1 khóa chính để update. Việc save có nghĩa là update 1 model vào realm. Nếu như nó chưa tồn tại thì thêm mới. Nếu nó đã có thì cập nhật vào model đã có sẵn.

OK, khi có realm manager này, các bạn sẽ dễ dàng làm những việc mà mục tiêu của đề bài đã yêu cầu.

Hãy cùng xem code từng dòng nhé:

Đầu tiên để chơi với realm, bạn cần phải cài đặt realm vào Podfile nhé:

Việc cài đặt rất đơn giản, bằng việc bạn mở Podfile ở project rồi thêm dòng 8 vào. Sau đó vào command line và cd vào thư mục source code, gõ: pod install là xong!

Khi cài xong rồi, bạn tạo 1 file có tên là RealmManager.swift, sau đó thêm dòng này vào trên cùng:

import Realm
import RealmSwift

2 dòng trên để các bạn có thể sử dụng realm.

/// Version realm database
enum RealmVersion: UInt64 {
    case version1 = 0
}

Dòng trên tôi dùng để update database cho realm. Mỗi khi tôi sửa lại model thì cần migration, liên quan đến cập nhật dữ liệu với model mới. Cái này bạn sẽ quan tâm sau, bài này tôi không nói.

Tôi muốn update database theo khóa chính nên bạn cần có 1 cái RealmRepresentable bắt buộc phải có 1 khóa chính, tôi đặt tên là uid mà mọi object Codable muốn sử dụng realm đều phải có, tôi dùng protocol để làm việc đó. RealmRepresentable giúp bạn luôn phải có khóa chính cho model, đảm bảo model luôn luôn cập nhật được. RealmRepresentable này kế thừa từ Object – một class của realm dùng để xử lý database giống như model. Nếu bạn đã làm qua với sqlite, thì bạn sẽ thấy ưu điểm của realm ở đây, là bạn có thể thao tác với realm như thao tác với model vậy. Không phải select, delete… những câu lệnh khó nhằn của sql.

Protocol các hàm cơ bản của realm service

RealmServiceProtocol là protocol chứa các hàm có thể có của 1 manager. Nhìn tên hàm bạn cũng có thể đoán được ý nghĩa của nó đúng không:

  • associatedtype Entity: Định nghĩa 1 kiểu trừu tượng, protocol generic placeholder. Nói cách khác nó áp dụng kiểu cụ thể tại thời điểm compile. Nó làm cho code clear hơn nha. Entity chính là model chúng ta cần lưu, cụ thể là class của model codable kế thừa từ Object của realm.
  • queryAll: Hàm này trả về toàn bộ mảng [Entity]
  • func query(with predicate: NSPredicate, sortDescriptors: [NSSortDescriptor]) -> [Entity]: Hàm này dùng để truy vấn với điều kiện nào đó và sắp xếp theo yêu cầu nào đó.
  • func save(entity: Entity) -> Bool: lưu 1 entity vào database.
  • func save(entities: [Entity]) -> Bool: Lưu 1 mảng entity vào database.
  • func delete(entity: Entity) -> Bool: Xóa 1 entity ra khỏi database.
  • func delete(entities: [Entity]) -> Bool: Xóa 1 mảng [Entity] ra khỏi database.
  • func deleteAll() -> Bool: Xóa tất cả Entity ra khỏi database.

Ô kê la, vậy là bạn đã hiểu chúng ta sẽ làm gì rồi đúng hông? tất nhiên protocol thì là define thui, còn bây giờ mới tới màn coding thật sự nè. Nào hãy vào lớp RealmManager để xem nhé 😘

Class RealmManager định nghĩa hơi khác 1 chút so với những gì bạn hay làm. Ở đây ta có toán tử <T: RealmRepresentable>, nghĩa là mọi lớp kế thừa lại lớp này đều phải có tính chất của RealmRepresentable, hay nói cách khác nó phải có khóa chính:

var uid: String { get }

RealmManager kế thừa RealmServiceProtocol, nghĩa là mọi hàm chúng ta vừa nói ở trên đều phải định nghĩa cho nó.

  • typealias Entity = T: Ở đây chúng ta định nghĩa kiểu của Entity là T, là RealmRepresentable, tức là các class có tính chất của RealmRepresentable
  • Realm.Configuration: Cấu hình tham số cho Realm.
  • var realm: Realm?: đối tượng realm mà chúng ta sẽ sử dụng để xử lý các tác vụ với database.
Bảng được tạo trong realm database

Trông nó na ná bảng của mysql đúng không các bạn 🥺🥰

Hàm lấy toàn bộ bảng [Entity]
  • 68->71, nếu không khởi tạo được realm thì trả về rỗng
  • Nếu không thì lấy mảng Entity và trả về
Hàm lưu Entity
  • Nếu như không khởi tạo được realm thì trả về false
  • Nếu không tiến hành lưu entity vào database theo dạng update, nghĩa là nếu chưa có thì thêm mới vào, còn nếu đã tồn tại thì cập nhật. Sau đó refresh lại realm database.
  • Giả sử quá trình lưu thất bại, tiến hành in lỗi. Ở đây các bạn có thể để lệnh print trong cặp #if DEBUG

Tương tự hàm save 1 chuỗi “func save(entities: [Entity]) -> Bool” cũng như trên, chỉ khác là lưu 1 mảng vào.

Hàm xóa 1 Entity
  • Tiến hành tìm kiếm entity có khóa chính uid, nếu tìm được thì tiến hành xóa và trả về true. Nếu không trả về false.
  • Trường hợp xóa lỗi cũng hiển thị lỗi và trả về false.

Tương tự hàm “func delete(entities: [Entity]) -> Bool” dùng để xóa 1 mảng entities.

Hàm xóa toàn bộ Entity
  • Tiến hành xóa toàn bộ entities trong database và trả về true. Nếu không xóa được báo lỗi và trả về false.

Ở đây các bạn để ý mình viết hàm có phần comment ở trên hàm. Nếu bạn muốn nó sinh tự động có thể đặt con trỏ chuột vào tên hàm và bấm tổ hợp “Option + Command + /”, khi các bạn bấm vào dấu ? bên phải Xcode thì nó ra hướng dẫn của hàm, rất tiện lợi cho việc sinh documents sau này.

Hướng dẫn chuẩn format của Apple

Vậy là chúng ta đã hoàn thành cơ bản lớp RealmManager, base cơ bản để xử lý database trong realm.

Bài đã hơi dài, nên chúng ta sẽ tạm thời dừng ở đây. Trong bài tiếp theo mình sẽ hướng dẫn viết lớp RealmGithubService kế thừa từ RealmManager để xử lý data trả về từ API nhé.

Cảm ơn các bạn đã theo dõi.

Code Toàn Bug

Code nhiều bug nhiều!

One thought on “Lập trình IOS: Triển khai MVVM cho project swift(phần 4): Tạo ứng dụng offline bằng realm database

Leave a Reply

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