
Contents
Chào bạn đến với series Lập trình iOS cho mọi người. Bài viết này hứa hẹn sẽ khá dài và trình bày những gì cần thiết để làm việc với Database trong iOS Project. Chủ đề hôm nay là Core Data và ở mức level cơ bản và CRUD một bảng trong cơ sở dữ liệu.
Kiến thức để vào bài là bạn nên tìm hiểu các khái niệm của Database:
- Database
- Table
- Record
- Property
- Relationship
Cần ghi nhớ Database khác với SQL.
Kiến thức về SQL thì biết về các lệnh SQL cơ bản.
- Select
- Insert
- Update
- Delete
Nếu mọi thứ đã ổn thì …
Hãy bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. CoreData là gì?
1.1. Định nghĩa
Core Data là một framework của chính chủ Apple. Core Data cung cấp các giải pháp để quản lý và tự động cho hệ cơ sở dữ liệu của ứng dụng.
Thoáng qua, bạn có thể suy nghĩ nó cũng tương tự biết bao nhiều các framework khác về Database. Nhưng Core Data sẽ có các đặc thù riêng và nó giúp cho bạn rất nhiều trong code.
Đầu tiên về bản chất lập trình mobile, thì bị giới hạn bởi 2 yếu tố:
- Hiệu năng
- Năng lượng
Nên việc lưu trữ dữ liệu cũng là một vấn đề quan trọng. Tránh đi việc bạn phải request server để lấy dữ liệu nhiều lần. Cho bạn một giải pháp để lưu trữ dữ liệu offline. Ngoài ra, bạn có thể đồng bộ dữ liệu với hệ quản trị cơ sở dữ liệu trên Server.
Thứ hai, việc tương tác với Database cũng không phải là chuyện đơn giản. Hai vấn đề bạn cần quan tâm nhiều nhất là:
- Thiết kế hệ quản trị cơ sở dữ liệu
- Tương tác với hệ quản trị cơ sở dữ liệu
Tới đây trong đầu nhiều bạn sẽ nghĩ tới SQL mà thôi. Tuy nhiên, ở đầu bài mình đã nói rồi. SQL chỉ là ngôn ngữ script (không biết chính xác không), được tích hợp vào nhiều ngôn ngữ khác để thực hiện các lệnh truy vấn tới Database.
Vì hầu hết dev iOS đều xuất thân làm việc với phía client là chính. Nên việc tương tác với Database là thế yếu. Tuy nhiên, Core Data sẽ giúp cho bạn và giải pháp nó đưa ra là:
Hướng đối tượng
Mọi thứ sẽ được ánh xạ và quy chiếu ra các đối tượng, thực thể. Và chúng nó tương tác với các đối tượng khác có trong ứng.dụng.
Cuối cùng đó là sự quản lý tập trung. Bạn không cần phải lo lắng khi có nhiều tương tác với Database cùng một lúc. Core Data sẽ chia ra nhiều lớp để đảm đương từng nhiệm vụ.
Và điều tạo ra sự khác biệt lớn nhất đó là:
Thiết kế và cấu hình Database với giao diện trực quan. Mọi thứ có thể kéo thả được và tự động sinh code.
1.2. Ánh xạ với Database
- Database sử dụng ở đây là sqlite. Hệ quản trị cơ sở dữ liệu sử dụng phổ biến nhiều trong giới lập trình mobile.
- Entity sẽ tương ứng với các table trong Database
- Attribute tương ứng với các field trong table
- Relationship tương ứng với các quan hệ giữa các table trong Database (chỗ này mình chưa chắc)
- Managed Object tương ứng với các record trong các table
- …
2. Core Data Stack
“Manage and persist your app’s model layer.”
(Apple)
Đây cũng được gọi là: thành phần chính của Core Data được sử dụng trong project. Nó bao gồm các class và các đối tượng của các class đó. Bao gồm 3 thành phần quan trọng nhất.
2.1. Persistent Store Coordinator
Như tên gọi của nó, đối tượng NSPersistentStoreCoordinator
tiếp tục lưu trữ dữ liệu vào đĩa và đảm bảo (các) lưu trữ liên tục và mô hình dữ liệu tương thích. Nó làm cầu nối trung gian tới phần Database lưu trữ (sqlite). Tạo ra một lớp dữ liệu ở giữa để tương tác với phần còn lại của project.
Hiểu một cách đơn gian nhất là nó là cầu nối cho các class/object với file database (thường sẽ là sqlite). Đảm bảo mọi lưu trữ được ổn định nhất.
2.2. Managed Object Model
Đây chính là mô hình (model) của Cơ sở dữ liệu của bạn trong project. Nhấn mạnh, nó không phải là cơ sở dữ liệu của bạn. Nhưng bạn có thể:
- So sánh mô hình đối tượng được quản lý với giản đồ của cơ sở dữ liệu
- Có chứa thông tin về các mô hình hoặc thực thể của biểu đồ đối tượng
- Thuộc tính của chúng và cách chúng liên quan đến nhau.
Đối tượng NSManagedObjectModel
biết về mô hình dữ liệu bằng cách tải một hoặc nhiều tệp mô hình dữ liệu ( *.xcdatamodel
file) trong quá trình khởi tạo của nó.
Ngoài ra, thể hiện trực quan của nó trong project chính là file *.xcdatamodel
. Giúp cho bạn:
- Thiết kế cơ sở dữ liệu
- Tạo các table (Entity)
- Tạo các thuộc tính
- Ràng buộc các quan hệ giữa các table.
- Cấu hình các table
- Mô hình trực quan giữa các table
Ta có thể ánh xạ sang Database như sau:
- Entity ==> Table
- Attribute ==> Field
- Relationship ==> Key/Relationship
Chốt lại
Để cho Persistent Store Coordinator biết được mô hình ở dưới Database như thế nào? Thì Managed Object Model đảm đương nhiệm vụ đó. Bên cạnh còn giúp người lập trình có thể thiết kế một cách trực quan nhất, mặc dù chã biết tí nào về Database.
2.3. Managed Object Context
Đối tượng NSManagedObjectContext
quản lý các đối tượng của Entity trên Data Model. Là các thể hiện của lớp NSManagedObject
.
Dễ hiểu thì việc bạn tương tác với Database, nếu phải nhớ các lệnh truy vấn SQL thì khá là đau đầu. Managed Object Context giúp bạn biến mọi dữ liệu trong Database thành các class và object.
Có 2 thành phần quan trọng nhất:
NSManagedObjectContext
là class quản lý toàn bộ các đối tượng liên quan tới dữ liệu của Database. Phụ trách thêm các công việc như:- Fetch
- Insert
- Update
- Delete
- Save
- …
NSManagedObject
đây là class để quản lý tới các record. Biến dữ liệu trong các record thành các đối tượng của nó. Bạn có thể tạo các class mới kế thừa lại để dễ thao tác trong code.
Hình dung sẽ như thế này:
- Table trong Database sẽ được biểu diển là các Entity trong Data Model. Và tiếp tục được biển diễn bằng các NSManagedObject trong code.
- Các thuộc tính, quan hệ cũng được biểu diễn thành các property trong sub-class của NSManagedObject.
- Với sub-class của NSManagedObject thì
- Class name sẽ là tên của Entity hay cũng là tên của table trong Database
- NSManagedObjectContext sẽ quản lý tất cả các lớp NSManagedObject.
Tới đây cũng khá nhiều lý thuyết được trình bày. Và tiếp tục sang phần tìm hiểu về cách hoạt động, thì chúng ta sẽ sử dụng code để trình bày và giải thích. Bài này thì sẽ tạo một iOS Project ngay từ đầu.
3. Hoạt động
3.1. Nguyên tắc chung
- Bước 1: Phải lấy được đối tượng Managed Object Context
- Bước 2: Setup các đối tượng cần sử dụng, như:
- Entity name : tên của các entity
- Sort descriptiors : các điều kiện sắp xếp
- Predicate : các điều kiện để query
- Cache: bộ đệm cho các lần thao tác
- Bước 3: Tạo request
- Fetched request
- Delete request
- …
- Bước 4: Tương tác với các NSManagedObject
- Add
- Update
- Delete
- Bước 5: Save context
- Lưu trữ lại lại các thay đổi xuống database
- Update lại giao diện
3.2. Làm việc với CoreData
Đây thực chất là một số lưu ý, mà bạn cần nhớ khi làm việc với Core Data.
- Toàn bộ thao tác đều thông qua các class và object.
- Mỗi class trong CoreData sẽ tương ứng với với thực thể/thuộc tính trong Database
- Các đối tượng luôn tồn tại duy nhất
- Presistent Store Coordinator
- Managed Object Context
- Mọi thao tác sẽ bị biến mất, nếu bạn không save nó lại.
- Cần impletment các delegate để biết được lúc nào có thay đổi về mặt dữ liệu, đê cập nhật giao diện
- Cẩn thận với các kiểu tham chiếu, nhất trong các trường hợp các Entity có các quan hệ với nhau.
- Mỗi lần update lại Data Model thì bạn nên xoá app và build lại từ đầu.
- …
3.3. SaveContext
Core Data sẽ tạo ra một lớp trung gian. Mọi thao tác tới Database đều được thực hiện ở lớp trung gian đó. Vì để đảm bảo việc thực hiện nhiều lần và liên tục các thao tác trên Database một cách ổn định nhất. Tránh crash chương trình….
Nếu như bạn không tiến hành gọi lưu trữ, thì dữ liệu của Database sẽ không được cập nhật.
Việc lưu trữ này sẽ được gọi tên là saveContext
. Nó cài đặt ở AppDelegate hoặc ở nơi mà đối tượng Presistent Store Coordinator được sử dụng.
func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } }
Việc saveContext
, sẽ thông qua đối tượng Managed Object Context. Vì vậy, quản lý được đối tượng Managed Object Context sẽ là nhiệm vụ được ưu tiên hàng đầu.
4. Create CoreData
Để bắt đầu làm việc với CoreData, thì bạn phải tạo project. Trong bài này sẽ trình bày 2 cách.
4.1. Tự động
Ưu tiên cho các project xây dựng ngay từ đầu. Xcode sẽ hỗ trợ bạn tạo các file và sinh code liên quan tới Core Data.
Mở Xcode và New Project
Chọn Use Core Data
. Khi chọn như vậy thì:
- file
*.xcdatamodel
sẽ được tạo thêm - AppDelegate sẽ có thêm code
- Core Data stack
- Core Data saving support
4.2. Bằng tay
Dùng cho các project đã có sẵn và muốn thêm Core Data vào. Hoặc lỡ new project rồi mà lại quên.
Trong Project, bạn chọn New File và chọn file Data Model và đặt tên theo ý đồ của bạn.
Tiếp theo là cập nhật các đoạn code cần thiết vào:
- File AppDelegate.swift
- Thêm Core Data
import CoreData
-
- Thêm presistent Container. Chú ý phần
name
, phải chính xác
- Thêm presistent Container. Chú ý phần
lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "DemoCoreData") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }()
-
- saveContext
func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } }
- Nếu dùng Xcode 11 và cấu hình có thêm
SceneDelegate
thì thêm function vào tiếp. Để đảm bảo dữ liệu được lưu trữ thường xuyên, khi app vào background.
func sceneDidEnterBackground(_ scene: UIScene) { (UIApplication.shared.delegate as? AppDelegate)?.saveContext() }
4.3. Entities & Attributes
Tiếp theo là bạn tới việc thiết kế Database của bạn. Phần này chắc cần xem hình là đủ hiểu rồi. Trong đó, chú ý 2 phần quan trọng nhất:
- Entities : để thiết kế các table
- Attributes : các thuộc tính cho entity
- Có thể quan sát thấy nhiều kiểu dữ liệu được support cho Core Data
Để quan sát trực quan giữa các Entity với nhau, bạn kích sang Editor Style ở góc dưới bên phải
Trong bài viết, mình tạo 1 entity duy nhất là
User
với các thuộc tính đơn giản. Với cấp độ của bài là basic, thì phần quan hệ và các tính năng cao cấp hơn thì mình sẽ không trình bày. Hẹn ở bài sau!
4.4 Codegen
Đây chính là tính năng tạo nên thương hiệu và sự khác biệt giữa Core Data với phần còn lại của thế giới văn mình loài người.
“Automatically or manually generate managed object subclasses from entities.”
(Apple)
Sau khi đã xong phần setup các đối tượng quản lý Core Data, thì tới phần thiết kế các Entity và các mối quan hệ. Tiếp theo, tạo các class liên quan tới các Entity. Các class này chính là sub-class
của NSManagedObject. Nó là sự ánh xạ của Entity lên code.
Xcode hỗ trợ việc bạn sinh code cho nó một cách tự động.
Ví dụ có thể xem qua class mẫu như sau:
// // Quake+CoreDataClass.swift // // This file was automatically generated and should not be edited. // import Foundation import CoreData @objc(Quake) public class Quake: NSManagedObject { }
// // Quake+CoreDataProperties.swift // // This file was automatically generated and should not be edited. // import Foundation import CoreData extension Quake { @nonobjc public class func fetchRequest() -> NSFetchRequest<Quake> { return NSFetchRequest<Quake>(entityName: "Quake") } @NSManaged public var magnitude: Float @NSManaged public var place: String? @NSManaged public var time: Date? @NSManaged public var countries: NSSet? } // MARK: Generated accessors for countries extension Quake { @objc(addCountriesObject:) @NSManaged public func addToCountries(_ value: Country) @objc(removeCountriesObject:) @NSManaged public func removeFromCountries(_ value: Country) @objc(addCountries:) @NSManaged public func addToCountries(_ values: NSSet) @objc(removeCountries:) @NSManaged public func removeFromCountries(_ values: NSSet) }
Lợi ích của nó là gì?
Đơn giản là cho bạn tuỳ chỉnh các class NSManagedObject theo ý muốn riêng của bạn. Tăng tính linh hoạt khi bạn vừa muốn dùng 1 class cho Core Data và cho nhiều việc khác trong project.
Cách tạo
Đầu tiên là xác định cấu hính cho các Entity.
Bạn chú ý sẽ có 3 lựa chọn cho mỗi Entity trong phần Data Model inspector:
- Class Definition
- Nếu như bạn không cần quan tâm gì nhiều tới việc custom hay logic của Entity
- File mã nguồn sẽ không có, nhưng Xcode sẽ tạo ra class
- Tự động cập nhật nếu có sự thay đổi ở Data Model
- Category/Extension
- Mếu muốn thêm các logic vào các sub-class Managed Object
- Là phần mở rộng nên bạn có thể toàn quyền thêm vào các thuộc tính và phương thức.
- Cần duy trì nó khi có sự thay đổi từ Data Model
- Manual/None
- Mọi thứ cần dùng và liên quan thì bạn tự làm hết, tự quản lý các sub-class Managed Object
- Cần phải tạo lại mỗi khi có sự thay đổi Data Model
- Thay đổi được class cũng như phần mở rộng của nó.
- Ưu tiên dùng cách này.
Bạn chọn Manual/None. Kích vào Menu > Editor > Create NSManagedObjectsn Subclass. Để tạo:
- Duyệt hết các Data Model
- Duyệt hết các Entity
Xong thì sẽ có 2 file tương ứng với mỗi Entity:
- User+CoreDataClass.swift
import Foundation import CoreData @objc(User) public class User: NSManagedObject { }
- User+CoreDataProperties.swift
import Foundation import CoreData extension User { @nonobjc public class func fetchRequest() -> NSFetchRequest<User> { return NSFetchRequest<User>(entityName: "User") } @NSManaged public var age: Int16 @NSManaged public var gender: Bool @NSManaged public var name: String? }
Tới đây thì coi như xong phần setup và tạo các class cho Core Data trong project của bạn rồi. Giờ tiến vào các phần làm việc với dữ liệu với Core Data và cập nhật chúng lên giao diện.
5. Fetched Result
Fetched Result == Query Database == SELECT (SQL)
Công việc đầu tiên của bạn, là lấy toàn bộ dữ liệu của một Entity được lưu và hiển thị nó lên giao diện. Và được chia làm 2 phần quan trọng:
- Lấy dữ liệu
- Hiển thị dữ liệu
5.1. Nguyên tắc
Tương tự như phần nguyên tắc chung ở trên. Và riêng cho phần Fetched Result thì có thêm một số phần nhỏ như sau:
- Bước 1: Lấy được đối tượng Managed Object Context
- AppDelegate
- Managed Object Context
- Bước 2: Tạo các đối tượng liên quan
- Entity name : tên của các entity
- Sort descriptiors : các điều kiện sắp xếp
- Predicate : các điều kiện để query
- Cache: bộ đệm cho các lần thao tác
- Bước 3: Tạo Fetched Request
- Bước 4: Fetch bằng đối tượng Managed Object Context
- Bước 5: Update UI (TableView hoặc CollectionView)
Code ví dụ
//Lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } //Lấy manage Object Context let managedContext = appDelegate.persistentContainer.viewContext //Tạo Fetched Request với Entity name let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person") //Fetch dữ liệu do { //Thêm vào array có sẵn people = try managedContext.fetch(fetchRequest) } catch let error as NSError { print("Could not fetch. \(error), \(error.userInfo)") } }
5.2. Fetched Result Controller
Đây là class NSFetchedResultController
, tạo ra một đối tưởng để quản lý results, từ việc thực thi một FetchedRequest. Và quan trọng, có thể dùng nó cho việc hiển thị dữ liệu lên giao diện.
Tiện ích mà nó mang lại:
- Đơn giản
- Quản lý các đối tượng tốt
- Tự động cập nhật được theo sự thay đổi từ Database
- Add/Update/Delete các đối tượng
- Tích hợp tốt với UI, nhất là sử dụng như một DataSource cho TableView/CollectionView
Quan trọng dùng tốt với các sub-class của NSManagedObject.
5.4. Sử dụng
Mở file HomeViewController
. Và thêm vào đối tượng Fetched Results Controller
var fetchedResultsController: NSFetchedResultsController<User>!
Viết function để khởi tạo đối tượng đó
//init fetch result func initializeFetchedResultsController() { // Create Fetch Request let fetchRequest: NSFetchRequest<User> = User.fetchRequest() // Configure Fetch Request fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] // lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { fatalError("Failed to initialize FetchedResultsController: \(error)") } }
Giải thích:
- Vì đã tạo sub-class của NSManagedObject là
User
. Trong đó đã có sẵn việc tạo FetchedRequset cho riêng Entity là User. - Việc sắp xếp thì trường
name
, của Entity - Không có bộ đệm và điều kiện truy vấn
- Xét thêm delegate của NSFetchedResultsController
Sử dụng vào DataSource của TableView:
- Array chứa dữ liệu là thuộc tính
fetchedObjects
của Fetched Results Controller - Truy cập tới đúng phần tử cần lấy, thì sử dụng function
fetchedResultsController.object(at: indexPath)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { fetchedResultsController.fetchedObjects!.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HomeCell let user = fetchedResultsController.object(at: indexPath) cell.nameLabel.text = user.name cell.ageLabel.text = "\(user.age) years old" cell.genderLabel.text = user.gender ? "Male" : "Female" return cell }
Để có dữ liệu cho việc test thì bạn cần thêm dữ liệu giả vào. Sử dụng function dummy data này:
//MARK: - Dummy Data func save(name: String, age: Int, gender: Bool) { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext let entity = NSEntityDescription.entity(forEntityName: "User", in: managedContext)! let user = NSManagedObject(entity: entity, insertInto: managedContext) user.setValue(name, forKeyPath: "name") user.setValue(age, forKeyPath: "age") user.setValue(gender, forKeyPath: "gender") do { try managedContext.save() } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } } }
Thực hiện việc thêm dữ liệu giả vào. Xong thì xoá những đoạn code này. Ví dụ:
save(name: "Tí", age: 10, gender: true) save(name: "Tèo", age: 12, gender: true) save(name: "Linh", age: 9, gender: false) save(name: "Trang", age: 8, gender: false)
OKE. Chạy thử chương trình và xem như thế nào.
Edit lại function initializeFetchedResultsController
với điều kiện cho truy vấn dữ liệu.
- Thêm
predicate
, cho fetchedRequest - Điều kiện là fetch với các User có giới tính là
Male
func initializeFetchedResultsController() { // Create Fetch Request let fetchRequest: NSFetchRequest<User> = User.fetchRequest() //Predicate fetchRequest.predicate = NSPredicate(format: "gender == true") // Configure Fetch Request fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] // lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() } catch { fatalError("Failed to initialize FetchedResultsController: \(error)") } }
Build và tận hưởng kết quả.
6. Insert ManagedObject
Insert ManagedObject == Add thêm 1 record trong Database == INSERT (SQL)
Công việc này là thêm một record vào trong Database. Và với Core Data, chúng ta chỉ cần thêm 1 object thôi. Còn lại mọi vấn đề Core Data sẽ giải quyết.
6.1. Nguyên tắc
- Bước 1: Lấy được đối tượng Managed Object Context
- AppDelegate
- Managed Object Context
- Bước 2: Tạo các đối tượng liên quan
- Entity name : tên của các entity
- Bước 3: Tạo 1 object của NSManagedObject và tiến hành gán giá trị cho các thuộc tính của nó.
- Bước 4: saveContext
- Bước 5: Update UI (TableView hoặc CollectionView)
Code ví dụ
func save(name: String, age: Int, gender: Bool) { //Lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } //lấy Managed Object Contexxt let managedContext = appDelegate.persistentContainer.viewContext //Tạo Entity Name let entity = NSEntityDescription.entity(forEntityName: "User", in: managedContext)! //New Object và insert vào Managed Object Context let user = NSManagedObject(entity: entity, insertInto: managedContext) //Gán giá trị cho các thuộc tính của object user.setValue(name, forKeyPath: "name") user.setValue(age, forKeyPath: "age") user.setValue(gender, forKeyPath: "gender") do { //lưu lại try managedContext.save() } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } }
6.2. Sử dụng
Cách trên thì đơn giản. Nhưng muốn để phát huy hiệu quả hơn, chúng ta cần tương tác với các object của các sub-class NSManagedObject.
Mở file NewViewController
và tạo một function để lưu lại đối tượng mới trong Database. Trong code demo thì các giá trí thuộc tính của đối tượng mới được lấy từ các UITextField trên giao diện.
@objc func done() { // lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext // lấy Entity name let entity = NSEntityDescription.entity(forEntityName: "User", in: managedContext)! // tạo 1 Managed Object --> insert let user = User(entity: entity, insertInto: managedContext) // set giá trị cho Object user.name = nameTextField.text user.age = Int16(ageTextField.text!) ?? 0 user.gender = genderSegmentedControl.selectedSegmentIndex == 0 ? true : false //save context do { try managedContext.save() self.navigationController?.popViewController(animated: true) } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } }
Các bước setup vẫn tương tự cách trên. Với bước tạo object mới thì sử dụng class User
. Khi đó ta có quản lý được kiểu giá trị được thêm vào các thuộc tính một cách chính xác và tường minh hơn.
Chuyển sang bước cập nhật dữ liệu lên giao diện. Thì có thể mô tả đơn giản như sau:
Insert một object –> saveContext –> Core Data báo 1 notification –> các Fetched Result Controller nhận được –> reload UI
Để chuẩn bị cho việc reload, cần phải cấu hình delegate
của Fetched Results Controller, sao cho nó nhận được các sự kiện từ CoreData/Managed Object Context phát đi.
Mở file HomeViewController
, thêm các delegate của NSFetchedResultsControllerDelegate
.
- will change content
- Nhận được khi toàn bộ dữ liệu sắp có sự thay đổi
- did change object
- Quan trọng nhất
- Để biết sự thay đổi của từng object ở từng vị trí
- did chang content
- Nhận được khi sau toàn bộ dữ liệu đã thay đổi
(Code mẫu ví dụ cho việc implement didChangObject
, các function khác bạn có thể tuỳ ý sử dụng)
- insert
- delete
- update
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { switch (type) { case .insert: print("insert") if let indexPath = newIndexPath { tableView.insertRows(at: [indexPath], with: .fade) } break; case .delete: print("delete") if let indexPath = indexPath { tableView.deleteRows(at: [indexPath], with: .fade) } break; case .update: print("update") tableView.reloadRows(at: [indexPath!], with: .automatic) break; default: print("default") } }
Tiến hành build và cảm nhận kết quả.
7. Edit ManagedObject
Edit ManagedObject == Thay đổi nội dung của 1 record trong Database == UPDATE (SQL)
Công việc này khá là đơn giản, thay đổi nội dung của một record. Và với Core Data, là thay đổi giá trị thuộc tính của một object.
7.1. Nguyên tắc
- Bước 1: Lấy được đối tượng Managed Object Context
- AppDelegate
- Managed Object Context
- Bước 2: Lấy object của NSManagedObject
- Bước 3: Thay đổi giá trị của các thuộc tính
- Bước 4: saveContext
- Bước 5: Update UI (TableView hoặc CollectionView)
7.2. Sử dụng
Tại file HomeViewController
, lấy đối tượng User
, để gán cho thuộc tính của EditViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let vc = EditViewController() //lấy đối tượng và gán vc.user = fetchedResultsController.object(at: indexPath) self.navigationController?.pushViewController(vc, animated: true) }
Tại EditViewController
, tiến hành thay đổi giá trị và khi người dùng nhấn nút để lưu lại, thì tiến hành lưu lại đối tượng.
@objc func save() { // lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext // set giá trị cho Object user.name = nameTextField.text user.age = Int16(ageTextField.text!) ?? 0 user.gender = genderSegmentedControl.selectedSegmentIndex == 0 ? true : false //save context do { try managedContext.save() self.navigationController?.popViewController(animated: true) } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } }
Vì ở HomeViewController
, đã setup việc nhận sự thay đổi bằng các delegate của Managed Object Context
. Nên fetchedResultController
và giao diện sẽ tự động cập nhât.
8. Delete ManagedObject
Delete ManagedObject == Xoá record trong Database == DELETE (SQL)
Công việc này là xoá đi 1 hoặc nhiều record có trong Database. Với Core Date, thì tương đương với việc:
- Xoá 1 object
- Xoá nhiều object
- Xoá cả Entity
8.1. Nguyên tắc
- Bước 1: Lấy được đối tượng Managed Object Context
- AppDelegate
- Managed Object Context
- Bước 2: Tạo Delete Fetch Request
- Entity name : tên của các entity
- Predicate : điều kiện để xoá
- Bước 3: Thực thi request bằng Managed Object Context
- Bước 4: saveContext
- Bước 5: Update UI (TableView hoặc CollectionView)
8.2. Delete 1 object
Khá là đơn giản, dùng hàm delete(:)
của đối tượng Managed Object Context. Tham khảo đoạn code ví dụ sau tại file HomeViewController
. Trong đó:
- Action xoá thông qua việc swipe của người dùng lên cell và nhấn nút
Delete
- Tiến hành xoá object bằng Managed Object Context
- Sau khi
saveContext
, thì CoreData sẽ tự động báo và delegate nhận được - Tiến hành remove cell (đã setup theo delegate ở trên rồi)
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { // lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext // lấy item ra để xoá let user = fetchedResultsController.object(at: indexPath) // delete managedContext.delete(user) //save context do { try managedContext.save() } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } } }
8.3. Delete all objects
- Cách 1: Bách nhục xuyên tâm
For
lần lượt các item củafetchedResultsController.fetchedObjects
- Xoá từng object
- Cách 2: dùng Batch Delete Request
- Tạo Fetched Request
- Tạo Batch Delete Request từ Fetched Request
- Thực thi request bằng Managed Object Context
// lấy AppDelegate guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // lấy Managed Object Context let managedContext = appDelegate.persistentContainer.viewContext // Create Fetch Request let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "User") // Initialize Batch Delete Request let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch) do { // execute delete try managedContext.execute(deleteRequest) // save try managedContext.save() // Perform Fetch try self.fetchedResultsController.performFetch() // Reload Table View self.tableView.reloadData() } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") }
Về Batch là gì, thì mình xin trình bày ở một bài khác.
Tới đây thì cũng rất là đủ skill cho bạn để chiến đấu với Core Data. 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ề Core Data
- Các thành phần trong Core Data
- Các thao tác CRUD đơn giản
- Tích hợp lên giao diện
- Nhận sự kiện khi có sự thay đổi từ Database
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
- Học vì tồn tại
- PARA – Phương pháp phân bổ tài nguyên giúp nâng cao hiệu quả sáng tạo
- Phù thủy phiên dịch ý tưởng
- XML Delimiters – Mở khóa thế giới prompt phức tạp
- Instructions – Cung cấp hướng dẫn cho các Gen AI
- SMART – Hướng dẫn dành tạo Prompt cho người mới bắt đầu
- Nhìn lại năm 2024
- CO-STAR – Công thức vàng để viết Prompt hiệu quả cho LLM
- Prompt Engineering trong 10 phút
- Một số ví dụ sử dụng Prompt cơ bản khi làm việc với AI
You may also like:
Archives
- January 2025 (7)
- December 2024 (4)
- 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)