Contents
Chào bạn đến với Fx Studio,
Bài viết này sẽ trình bày về một framework làm việc với cơ sở dữ liệu trong ứng dụng iOS. Đó là Realm. Nhưng chúng ta chỉ tìm hiểu về Realm Swift mà thôi.
Trước khi vào bài thì bạn có thể ghé đọc thêm về Core Data, hàng chính chủ của Apple để làm việc với cơ sở dữ liệu trong ứng dụng. Còn bây giờ thì …
Bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Realm
Khi bạn làm việc với cơ sở dữ liệu thì:
- SQLite khá là thô sơ, cần phải quản lý thêm rất nhiều.
- Core Data mặc dù là hàng chính chủ. Nhưng rất rườm ra và khó hiểu
Và bạn muốn có
Một giải pháp thay thế.
Realm Platform & Realm Database
- Realm Platform là một lớp dữ liệu cho các cross-platform app.
- Realm Platform là một tổ hợp của Realm Object Server và các client SDK mà hoạt động đồng bộ Realm.
- Trong đó Realm Database là phần quan trọng nhất của Realm Platform. Nó là một mã nguồn mở, là một thư viện database đã được optimize để sử dụng cho mobile.
Và Realm Database là cơ sở dữ liệu mã nguồn mở, miễn phí trên mobile (hiện nay đã hộ trợ Android và iOS, trong tương lai gần sẽ có thêm React Native và Xamarin)
Tất nhiên, là bạn sẽ có câu hỏi “Tại sao phải dùng Realm?“. Nhưng trong phạm vi bài này, mình sẽ không trả lời. Hi vọng bạn tìm hiểu hết Realm thì sẽ rút ra được câu trả lời cho riêng bản thân. Và có nên hay không nên sài nó trong iOS Project.
Tham khảo:
2. Cài đặt
Có rất nhiều cách cài đặt và mình sẽ hướng dẫn cách dễ nhất.
Trước tiên cần chuẩn bị cho iOS Project của bạn. Và cài đặt CocoaPod cho nó. Mình đã trình bày ở các bài viết sau:
- Nâng cao: Setup MacOS for developer
- Nâng cao vừa vừa: Cài đặt bundle cho iOS Project
- Đơn giản vô cùng: Basic iOS tutorial : Project Template
Sau khi đã cài đặt CocoaPod và cấu hình xong project. Bạn mở Podfile lên vào thêm dòng lệnh này vào:
pod 'RealmSwift'
Mở Terminal lên, cd
tới thư mục của project và chạy lệnh
pod install
Chờ một chút cho lần cài đặt đầu tiên. Sau khi đã thành công, bạn mở file *.xcworkspace
và thử import RealmSwift
xem được chưa. Nếu OK thì mình chuyển sang việc tạo Models cho Database.
3. Realm Model
3.1. Create
Để tạo một Realm model chứa dữ liệu, thì cần tạo một subclass
của Object hoặc của model có sẵn.
Đối tượng của Realm model giống các đối tượng trong Swift. Điểm đặc biệt là trên thread mà nó được tạo ra, chỉ có thể sử dụng duy nhất một đối tượng và không cần refresh object.
Ví dụ:
import RealmSwift // Dog model class Dog: Object { @objc dynamic var name = "" @objc dynamic var owner: Person? // Properties can be optional } // Person model class Person: Object { @objc dynamic var name = "" @objc dynamic var birthdate = Date(timeIntervalSince1970: 1) let dogs = List<Dog>() }
3.2. Property
- Yêu cầu: Tất cả các non-optional property cần phải có giá trị sau khi
init()
chạy. - Realm support các kiểu:
- Bool
- Int, Int8, Int16, Int32, Int64
- Double
- Float
- String
- Date
- Data
- Đối với kiểu String, Date, Data khai báo
optional
với cú pháp thông thường. - Các kiểu còn lại khai báo optional với RealmOptional<T>.
- Tất cả các property lưu lại Realm đều phải được khai báo
dynamic
var. - Trừ LinkingObjects, List và RealmOptional được khai báo với
let
.
3.3. Primary Key
class Person: Object { @objc dynamic var id = 0 @objc dynamic var name = "" override static func primaryKey() -> String? { return "id" } }
Là khoá chính cho một Model trong lưu trong Realm. Dùng để truy xuất dữ liệu & quan hệ.
3.4. Indexing properties
class Book: Object { @objc dynamic var price = 0 @objc dynamic var title = "" override static func indexedProperties() -> [String] { return ["title"] } }
Tương tự như khoá chính. Nói chung dùng cho nó xịn xò và tăng tốc độ truy vấn. Tránh lặp các item được lưu hay filter.
3.5. Ignoring properties
class Person: Object { @objc dynamic var tmpID = 0 var name: String { // read-only properties are automatically ignored return "\(firstName) \(lastName)" } @objc dynamic var firstName = "" @objc dynamic var lastName = "" override static func ignoredProperties() -> [String] { return ["tmpID"] } }
Chỉ định cho thuộc tính nào không cho phép lưu vào Realm.
3.6. Relationship
- To-one relationship:
- Ví dụ: 1 em chó chỉ có một chủ
class Dog: Object { // ... other property declarations @objc dynamic var owner: Person? // to-one relationships must be optional }
- To-many relationship:
- Ví dụ: 1 ông chủ nuôi nhiều em chó
class Person: Object { // ... other property declarations let dogs = List<Dog>() }
- Ví dụ cách dùng
- Ông chủ là
jim
- Em chó là
rex
- Sau đó ông chủ nuôi thêm một em cho là
Fido
- Ông chủ là
let jim = Person() let rex = Dog() rex.owner = jim let someDogs = realm.objects(Dog.self).filter("name contains 'Fido'") jim.dogs.append(objectsIn: someDogs) jim.dogs.append(rex)
- Một số tính chất của list:
- List tương tự như Array, các đối tượng được chứa trong list có thể được truy cập thông qua chỉ số.
- List chứa Object, và các kiểu dữ liệu nguyên thuỷ đã được đề cập.
- Tuy nhiên query trong list chứa dữ liệu nguyên thuỷ chưa được support.
- List đảm bảo thứ tự các đối tượng theo thứ tự được lưu vào.
4. Realm Write
Để tìm hiểu và diễn đạt phần này, mình sẽ trình bày ở cùng 1 project. Với:
- HomeViewController có
- 1 TableView để hiển thị danh sách các cuốn sách
- Có 2 chức năng : thêm mới và xoá tất cả sách
- DetailViewController có
- 2 trạng thái: thêm mới một cuốn sách và edit thông tin một cuốn sách
4.1. Write
All changes to an object (addition, modification and deletion) must be done within a write transaction.
Khi bạn tao thác với Database, lúc đó thao tác của bạn được gọi là một transaction. Thì Realm yêu cầu bạn phải thực hiện nó trong một lệnh write
.
Để đảm bảo các hoạt động thao tác lên Database một cách đồng bộ. Nếu như ở thread A bạn thay đổi dữ liệu. Thì nó phải được hoàn thành trước khi thread B thay đổi dữ liệu. Nhằm múc đích cuối là
Tránh crash chương trình.
4.2. Query
Ta có một file Realm Model cho sách như sau:
- Tạo một file swift tên là
Book.swift
import RealmSwift final class Book: Object { @objc dynamic var title = "" @objc dynamic var subTitle = "" @objc dynamic var price = 0 }
Tiến hành lấy một mãng các đối tượng lưu trong Realm thì chúng ta xem đoạn code sau ở file HomeViewModel
func fetchData(completion: (Bool) -> ()) { do { // realm let realm = try Realm() // results let results = realm.objects(Book.self) // convert to array books = Array(results) // call back completion(true) } catch { // call back completion(false) } }
Giải thích:
- Để tương tác với Realm thì bạn cần phải tạo đối tượng Realm
- Sử dụng function
.objects
với Model Type làBook
để lấy các đối tượng đã lưu lên. - Kết quả lấy được là một đối tượng
Result
, nên bạn cần phải convert sang Array mong muốn - Sử dụng closure để call back về ViewController
Khi làm việc với Realm thì cần phải sử dụng try catch
, quản lý các error.
Sau đây, là ví dụ được dùng ở ViewController khi muốn lấy dữ liệu từ Realm thông qua ViewModel.
func fetchData() { viewmodel.fetchData { (done) in if done { self.updateUI() } else { print("Lỗi fetch data từ realm") } } }
Build và tận hưởng kết quả:
Filter
Sử dụng function filter
. Để thêm điều kiện cho query dữ liệu. Ví dụ bạn muốn lấy các cuốn sách có giá lớn hơn 1 vnđ
let results = realm.objects(Book.self).filter("price > 1")
4.3. Insert
Code ví dụ đơn giản để thêm một đối tượng vào Realm.
func addBook(title: String, subTitle: String, price: Int) { // tạo realm let realm = try! Realm() // tạo 1 book let book = Book() book.title = title book.subTitle = subTitle book.price = price //realm write try! realm.write { realm.add(book) } }
Để chạy được như hình kết quả ở trên, bạn cần thêm vài đối tượng trước. Sau đó mới tiến hành query chúng nó.
addBook(title: "Mắt Biếc", subTitle: "Truyện dài tình cảm", price: 100000) addBook(title: "Đắc nhân tâm", subTitle: "Sách giúp đời giúp người", price: 100000)
Giải thích:
- Vẫn là đối tượng Realm huyền thoại kia.
- Lần này sử dụng
try!
để lược bỏ đi phầncatch
(không khuyến cáo bạn dùng) - Trước tiên là bạn cần phải tạo đối tượng cần lưu ra trước
- Gán giá trị cho các thuộc tính của nó
- Cuối cùng thực hiện lưu bằng function
.add
. Quan trọng thì phải thực hiện ởrealm.write
Code sử dụng trong project. Với dữ liệu lấy từ các UITextField của DetailViewController
- Function này dùng chung cho 2 trường hợp: add & edit
- Try catch đầy đủ các trường hợp
@objc func done() { guard let title = titleTextField.text, let subTitle = subTitleTextField.text, let price = Int(priceTextField.text ?? "0") else { return } if isNew { //add new a item do { // realm let realm = try Realm() // book let book = Book() book.title = title book.subTitle = subTitle book.price = price // add to realm try realm.write { realm.add(book) } self.navigationController?.popViewController(animated: true) } catch { print("Lỗi thêm đối tượng vào Realm") } } else { //edit item } }
Vì hiện tại chưa có cài đặt function tự động update lại danh sách của HomeViewController. Nên bạn cần build lại và xem kết quả.
4.4. Update
Việc update dữ liệu của một đối tượng lưu vào Realm thì khá là đơn giản. Đầu tiên bạn cần chú ý việc thêm primary key
cho Realm Model của bạn.
- Update lại model
Book
import RealmSwift final class Book: Object { @objc dynamic var title = "" @objc dynamic var subTitle = "" @objc dynamic var price = 0 override static func primaryKey() -> String? { return "title" } }
Sau đó tiến hành lấy một đối tượng từ mãng đối tượng đã query. Hoặc tuỳ ý bạn lấy như thế nào, miễn là nó phải từ Realm lên. Sau đó tiến hành gán lại giá trị cho các thuộc tính trong function realm.write
// realm let realm = try Realm() // edit book try realm.write { book.subTitle = subTitle book.price = price }
Code ví dụ cho màn hình DetailViewController của project trong bài:
- Gán đối tượng Realm (tại HomeViewController > DetailViewController)
- Tại
cell for row
, lấy 1 đối tượng ra theo thứ tự từ indexPath - Gán cho thuộc tính
book
của DetailViewController
- Tại
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let vc = DetailViewController() vc.isNew = false vc.book = viewmodel.getBook(at: indexPath) self.navigationController?.pushViewController(vc, animated: true) }
- Edit nó
- Viết vào chung function
done
ở trên
- Viết vào chung function
//edit item do { if let book = book { // realm let realm = try Realm() // edit book try realm.write { book.subTitle = subTitle book.price = price } self.navigationController?.popViewController(animated: true) } } catch { print("Lỗi edit đối tượng") }
Cũng như trên, chúng ta chưa cài đặt việc update dữ liệu tự động cho HomeViewController. Nên phải build lại xem kết quả.
4.5. Delete
Delete 1 đối tượng
Cũng là khá đơn giản. Sử dụng hàm delete
của Realm.
Ví dụ cho xoá đối tượng ở màn hình DetailViewController
@IBAction func deleteItem(_ sender: Any) { do { if let book = book { // realm let realm = try Realm() // edit book try realm.write { realm.delete(book) } self.navigationController?.popViewController(animated: true) } } catch { print("Lỗi Delete đối tượng") } }
Delete tất cả đối tượng
Đó chính là việc xoá nhiều đối tượng một lúc. Ta có nhiều lựa chọn như sau:
Các kiểu dữ liệu có thể dùng được như List, Results hay Array. Trong ví dụ sau thì chúng ta sẽ dùng Results.
Mở file HomeViewModel
, thêm function sau:
func deleteAll(completion: (Bool) -> ()) { do { // realm let realm = try Realm() // results let results = realm.objects(Book.self) // delete all items try realm.write { realm.delete(results) } // call back completion(true) } catch { // call back completion(false) } }
Giải thích:
- Các cài đặt và tạo đối tượng Realm vẫn như trên
- Lấy
results
ra trước với Type làBook
- Tiến hành gọi hàm
delete
với đối số là results - Vẫn phải thực hiện ở
realm.write
- Xong sẽ gọi
call back
về HomeViewController.
Code ví dụ ở HomeViewController khi thực hiện yêu cầu xoá tất cả:
@objc func deleteAll() { viewmodel.deleteAll { (done) in if done { self.fetchData() } else { print("Lỗi xoá tất cả đối tượng") } } }
Trong trường hợp này, thì chúng ta có thể gọi fetch
dữ liệu từ Realm. Và khi fetch xong thì giao diện cũng sẽ cập nhật lại. OK, build và cảm nhận. Nên bị crash thì cẩn thận debug lại nha.
5. Realm Notification
Các phần trình bày ở trên thì bạn cũng đã thấy có một vấn đề nan giải. Đó là cập nhật lại giao diện khi tương tác với Database. Ở đây cho các trường hợp sau:
- Insert
- Update
- Delete
Bây giờ, chúng ta sẽ tiến hành giải quyết vấn đề nan giải này.
Vì Realm được xem là một Mamager Model, chuyên phụ trách việc lưu trữ và thao tác với dữ liệu. Nên để biết được sự thay đổi từ Database thì bạn phải cài đặt các phương thức lắng nghe sự kiện từ Realm. Người ta gọi là
Observe
Ta sẽ có 2 bước quan trọng nhất:
5.1. Cài đặt Observe
Realm cung cấp cho chúng ta một function để lắng nghe sự kiện từ nó:
observe(_ block: @escaping (RealmCollectionChange<Results>) -> Void) -> NotificationToken
Để dễ hiểu thì mở project ví dụ lên, mở file HomeViewModel
. Tiến hành thêm function sau:
- Thêm một property
NotificationToken
private var notificationToken: NotificationToken?
- Thêm function setup Observe
func setupObserve() { let realm = try! Realm() notificationToken = realm.objects(Book.self).observe({ (change) in //update here }) }
Mỗi lần có sự thay đổi lên dữ liệu với kiểu là Book
, thì Realm sẽ phát đi một notification. Nếu bạn cần quan tâm cụ thể thay đổi theo kiểu gì thì có thể switch case
biến change để biết được trạng thái.
- Nó có kiểu là
RealmCollectionChange
5.2. Cài đặt update UI
Vì ViewController quản lý việc updateUI, nên từ ViewModel cần có protocol để báo cho ViewController biết lúc nào nên updateUI. Ta tiếp tục thêm các đoạn code sau file HomeViewModel
- Enum để phân biệt các trạng thái mà ViewModel yêu cầu
- Tuỳ ý bạn mà có thể khai báo thêm các trạng thái khác, như
- Error
- Change root
- …
- Tuỳ ý bạn mà có thể khai báo thêm các trạng thái khác, như
enum Action { case reloadData }
- Protocol của ViewModel
- Dùng để thông báo các trạng thái cho ViewController
- Bạn có thể sử dụng nó cho nhiều việc khác, như:
- Hiển thị Alert để thông báo lỗi
- Chuyển view
- Điều hướng
protocol HomeViewModelDelegate: class { func viewModel(_ viewModel: HomeViewModel, needperfomAction action: HomeViewModel.Action) }
- Cập nhật lại Realm Observe
- Khi có thay đổi thì
delegate
sẽ gọi function với trạng thái làreloadData
- Khi có thay đổi thì
func setupObserve() { let realm = try! Realm() notificationToken = realm.objects(Book.self).observe({ (change) in self.delegate?.viewModel(self, needperfomAction: .reloadData) }) }
- Implement ở ViewController
- Cứ nhận được delegate thì ViewController sẽ gọi lại
fetchData
, khi đó sẽ có dữ liệu mới cung cấp cho giao diện
- Cứ nhận được delegate thì ViewController sẽ gọi lại
extension HomeViewController : HomeViewModelDelegate { func viewModel(_ viewModel: HomeViewModel, needperfomAction action: HomeViewModel.Action) { fetchData() } }
- Cài đặt
viewmodel
của HomeViewController- Nên gọi ở
viewDidLoad
- Nên gọi ở
viewmodel.delegate = self viewmodel.setupObserve()
OKE, tới đây bạn build app và cảm nhận kết quả. Nếu có bug thì cẩn thận debug nha. Làm việc với Realm thì cần kiên nhẫn.
6. Realm Studio
Đây là một tool khá là hữu ích của Realm, giúp bạn rất nhiều điều. Bạn có thể xem thêm tại đây . Một số công dụng như sau:
- Xem được Database
- Quản lý nó
- Edit
- ….
Giúp cho bạn có thể debug nhanh Project khi làm việc với dữ liệu của Realm. Đó là điều quan trọng nhất.
Mình sẽ không hướng dẫn sử dụng Realm Studio trong bài viết này.
Tới đây, bạn đã nắm được cơ bản cách dùng Realm cho các yêu cầu hết sức cơ bản khi thao tác dữ liệu với Database. Bạn có thể checkout mã nguồn tại đây. Chúc bạn thành công!
Tạm kết
- Tìm hiểu về Realm
- Sử dụng Realm Swift với các thao tác cơ bản (Query, Insert, Update, Delete)
- Realm Notification
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)