Coding with Core Data in Xcode
Tags: swift, mobile, beginners
Published: 2023-11-13 16:02:23

**When do we need local device storage in iOS?**
- for cache
- when you are not connected to the internet, you still have local data available in your app

**Local Data Persistence**
- this data will be available whenever you open it
- provided by the **CoreData framework**

- SQLite relational database will be used 
- we won't be writing sql directly
- we will be using library

**What is relational database?**

![ERD](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25u4tm2gkdxh90gu2tj5.png)

- Entity Relational Diagram
- relationship
- entities: tables
- attributes: table columns

![entity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwrm7lp2nazxp2evshdv.png)

![entity](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3w969xb377xyinstz40l.png)

- core data will take care of database, table...
- all you need to do is to define entity, class and attributes for the entity
- instance of an entity(class): represent a row in the table
- map relationship between core data class and sqlite classes

**Steps to use Core Data framework**
1. define the entities
2. specify the attributes
3. describe relationships between the entities
4. insert data

**Core data**
- not a database
- act as an <u>interface between SQLite database and your app!</u>

1.**managed object context** - interact with app
2.**persistent container** - interact with database tables

**How to use SQLite database in xCode?**
**1.create a project and check "Use Core Data"**
![1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q54xw570fpcogv25cxfx.png)

below file will be generated
![1-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ob686ldu4xs6aqb9l8b2.png)

**2.click on core data file (w10c1_CoreDataApp) and + Add Entity**
![2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6r3pqkx5io47nzm2n7g.png)

double-click Entity and rename it to `Employee`
![2-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kgjx5svb0vznjsje2aq.png)

![2-2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zcq9wrddgk0e0yiic1w.png)

**3.add attributes to the `Employee` entity**
![3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obtsfsribnq87mkqprz1.png)

make sure you check `Manual/None` for Codegen (Entity > Inspector > Class > Codegen)
![3-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8g5pi1qnup7vge7m41v.png)

_why Manual/None?_
- core data will automatically generate a class for you in memory which you will be not seeing physically
- if you want to make sure class exists, do not let core data generate the class for you
- manual/none -> you will have controls over the class
- set to class definition -> core data will also generate the class as well (hard to find bugs since you don't see the class)

**4.select `Editor > Create NSManagedObject Subclass`, which generates a swift class representing your Entity**
![4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/05rgdqeb3hgu4d62zl4o.png)

![4-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phunm5sxrxqrzwu6x0kf.png)

![4-2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqjyhwsq9nksuuh5f8ud.png)

result - you should get two new files
![result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aexvbd4bh147nri6ewtx.png)

**5.need a reference to Core Data's managed context variable for CRUD operations**

```swift
class ViewController: UIViewController {
  
  let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

  override func viewDidLoad() {
    super.viewDidLoad()
  }
}
```

**6.CRUD operations**

**Create**
```swift
    override func viewDidLoad() {
        super.viewDidLoad()

        addEmployee(name: "John", age: 30, department: "Frontend")
    }

  func addEmployee(name: String, age: Int, department: String) {
    // 1. Create an employee object
    let emp = Employee(context: context)
    
    // 2. Set the properties of that object
    emp.name = name
    emp.age = Int16(age)
    emp.department = department
    
    // 3. Use the context variable to save the Employee to the database table
    do {
      try context.save()
      print("Employee saved!")
    } catch {
      print("Saved failed.")
    }
  }
```

**_How to locate the Core Data SQLite file?_**
1.Install this software in your Mac: https://sqlitebrowser.org/

2.add path code to `AppDelegate.swift > didFinishLaunchingWithOptions`

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  
  let path = FileManager
    .default
    .urls(for: .applicationSupportDirectory, in: .userDomainMask)
    .last?
    .absoluteString
    .replacingOccurrences(of: "file://", with: "")
    .removingPercentEncoding
  print("Core Data DB Path :: \(path ?? "Not found")")
  
  return true
}
```

3.run the app and get the path from the terminal
```shell
Core Data DB Path :: /Users/<USER>/Library/Developer/CoreSimulator/Devices/DA892F2C-3C83-4ED4-B5A2-D6D80257BDFC/data/Containers/Data/Application/4DE2AF5B-B77B-412F-B74D-AC3BAA023A8D/Library/Application Support/
```

4.open `Finder > Go > Go to Folder` and copy and paste the path into window
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ol07bdi29w97zl4smron.png)

5.open `.sqlite` file in `DB Browser`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y57wncfnnw2yh9dd42wb.png)

**Read**
```swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    fetchAllEmployees()
    fetchAllEmployees(age: 30)
  }

func fetchAllEmployees() {
    let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
    
    do {
      let employees = try context.fetch(fetchRequest)
      
      for emp in employees {
        print(emp.name ?? "")
        print(emp.age)
        print(emp.department ?? "")
        print("-----------")
      }
    } catch {
      print("Failed to fetch employees")
    }
  }

func fetchAllEmployees(age: Int) {
    let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
    fetchRequest.predicate = NSPredicate(format:"age <= %@", "\(age)")
    
    do {
      let employees = try context.fetch(fetchRequest)
      
      for emp in employees {
        print(emp.name ?? "")
        print(emp.age)
        print(emp.department ?? "")
        print("-----------")
      }
    } catch {
      print("Failed to fetch employees")
    }
  }
```

**Update**
```swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    updateEmployee()
  }
  
  func updateEmployee() {
    let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
    
    do {
      let employees = try context.fetch(fetchRequest)
      
      let firstEmp = employees.first
      firstEmp?.age = 30
      
      try context.save()
      print("Employee updated")
    } catch {
      print("Failed to update employee")
    }
  }
```

**Delete**
```swift
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    deleteEmployee()
  }
  
  func deleteEmployee() {
    let fetchRequest: NSFetchRequest<Employee> = Employee.fetchRequest()
    
    do {
      let employees = try context.fetch(fetchRequest)
      
      if let lastEmp = employees.last {
        context.delete(lastEmp)
      }
      
      try context.save()
      print("Employee deleted")
    } catch {
      print("Failed to delete employee")
    }
  }
```