[iOS] CoreData 사용하기

Han's·2023년 7월 1일
5
post-thumbnail

CoreData 사전 준비

CoreData를 사용하면 디바이스에 데이터를 영구적으로 저장할 수 있습니다.


먼저 프로젝트에서 Command + N을 눌러서 Data Model을 추가하시면 됩니다.
(프로젝트를 생성하면서 Use Core Data를 체크하셨다면 넘어가셔도 됩니다.)
파일 추가를 하시면 아래와 같이 모델명.xcdatamodeled가 생성된 것을 볼 수 있습니다.
이제 Entity를 만들어야 하는데 하단에 Add Entity 버튼을 클릭하시면 됩니다.

버튼을 클릭하시면 아래와 같이 Entity가 생성이 된 것을 볼 수 있습니다. (저는 Plan으로 이름을 변경했습니다.)

이제 Attribute를 추가해 줘야 합니다. 아래 사진에서 빨간색으로 표시된 것 중 아무거나 클릭하시면 됩니다.

클릭하시면 아래와 같이 Attribute가 추가된 것을 볼 수 있습니다.
저장하려는 데이터에 맞춰서 Attribute와 Type을 설정할 수 있습니다.

저는 플랜을 저장하기 위해 uuid(고유 식별자), date(날짜), content(내용), done(완료)가 필요해서 아래와 같이 저장할 정보를 입력했습니다. (저장할 정보에 따라 Attribute를 추가하고 Type을 지정하면 됩니다.)
❗수정, 삭제를 할 때 변하지 않는 고유한 값으로 찾는 것이 좋기 때문에 UUID와 같은 고유 식별자를 Attribute로 넣는 것이 좋습니다.

Attribute 추가를 다 하셨다면 Editor -> Create NSManagedObject...를 선택하시면

아래와 같이 파일이 생성된 것을 확인할 수 있습니다.

프로젝트를 만들 때 Use Core Data를 체크하셨다면 AppDelegate 아래쪽에 아래와 같은 코드가 추가된 것을 볼 수 있습니다. 여기서 container에 NSPersistentContainer(name: _)에서 name에는 Model 명을 넣어주시면 됩니다. (저는 모델명이 PlanBee라서 넣었습니다.)

만약 프로젝트를 만들 때 Use Core Data를 체크를 하지 않아서 AppDelegate에 코드가 없으시다면 아래의 코드를 넣으시면 됩니다.

// MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "여기에 Model 이름을 넣으면 됩니다.")
        container.loadPersistentStores(
            completionHandler: { (storeDescription, error) in
                if let error = error as NSError? {
                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
            })
        return container
    }()

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

여기까지가 사전 Core Data 사용을 위한 사전 준비 작업이었고 이제 사용해 봐야겠죠?


Core Data 사용하기

저는 Core Data를 따로 관리할 수 있게 CoreDataManager 파일을 만들어서 관리하도록 하겠습니다.
그전에 먼저 viewContext를 가져와야 합니다.

// AppDelegate 내부에 있는 viewContext를 호출하는 코드
 private static let context: NSManagedObjectContext? = {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            print("AppDelegate가 초기화되지 않았습니다.")
            return nil
        }
        return appDelegate.persistentContainer.viewContext
    }()

viewContext는 아까 AppDelegate에서 생성된 코드에 있는데 꺼내오기 위해 위와 같이 코드를 작성해서 가져오면 됩니다.

이걸 왜 가져와야 할까요??

AppDelegate에 있는 persistentContainer를 타고 들어가 보면 아래와 같이 viewContext가 있는데 viewContext는 NSManagedObjectContext입니다.

또다시 NSManagedObjectContext를 타고 들어가 보면 여러 가지 메서드가 있는데 우리가 원하는 생성, 수정, 삭제, 변경을 위한 메서드가 있기 때문입니다!
한마디로 CRUD를 하려면 viewContext가 꼭 필요하기 때문에 가져오는 것입니다.

저장(Save)

저장하기 위해 어떤 Entity에 저장하는지 알아야 합니다. (저는 Entity명이 Plan이라서 넣어준 것입니다.)

guard let context = context else { return }
guard let entity = NSEntityDescription.entity(
	forEntityName: "Plan", in: context
) else { return }

다음으로 저장하기 위한 Entity객체를 생성합니다.
사용법은 UserDefaults와 비슷하게 사용하면 됩니다.

setValue에 Attribute와 Type에 맞게 세팅하면 됩니다.

let object = NSManagedObject(entity: entity, insertInto: context)
        object.setValue(UUID(), forKey: "uuid")
        object.setValue(Date.now, forKey: "date")
        object.setValue("Core Data 테스트", forKey: "content")
        object.setValue(true, forKey: "done")

이제 저장을 해야겠죠? 저장은 실패할 수 있으니까 do - catch문을 사용하여 저장합니다.

do {
	try context.save()
} catch {
	print("error: \(error.localizedDescription)")
}

이렇게 하면 저장은 완성입니다!
이제 저장을 하고 읽어서 확인을 해야겠죠?

읽기(Read)

읽기, 수정, 삭제를 하려면 어떤 Entity에서 할 것인지 검색부터 해야겠죠?
fetchRequest는 Core Data에서 객체를 검색하는 데 사용되기 때문에 생성해 줍니다.

static func fetchData() {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Plan")
        
        do {
            guard let planList = try context.fetch(fetchRequest) as? [Plan] else { return }
            planList.forEach {
                print($0.uuid)
                print($0.date)
                print($0.content)
                print($0.done)
            }
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }

수정(Update)

고유 식별자 UUID를 파라미터로 받아서 수정한다는 가정으로 코드를 작성해 보겠습니다.

수많이 저장되어 있는 값들 중 각자가 유일한 값을 가지고 있으면 쉽게 찾을 수 있겠죠? UUID는 유일성을 보장하는 고유한 값이기 때문에 위의 글에서 Attribute에 UUID를 넣는 것이 좋다고 한 이유입니다. (UUID를 사용하여 필터링하기 위함)

static func updateData(id: UUID) {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>.init(entityName: "Plan")
//        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Plan")
        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id.uuidString)
//        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id as CVarArg)
        
        do {
            guard let result = try? context.fetch(fetchRequest),
                  let object = result.first as? NSManagedObject else { return }
            object.setValue("수정할 내용", forKey: "content")
            object.setValue(Date.now, forKey: "date")
            object.setValue(false, forKey: "done")

            try context.save()
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }

읽기에서 말했듯이 읽기, 수정, 삭제를 하려면 어떤 Entity에서 할 것인지 검색부터 해야겠죠?
수정에서도 똑같이 fetchRequest를 생성해 줍니다. 이때 주석 처리된 부분처럼 사용하는 방법도 있습니다.

fetchRequest.predicate는 검색 조건을 지정하는 것입니다.
이 부분도 마찬가지 주석 부분과 같이 CVarArg로 UUID를 Unwrapping 하여 사용하셔도 됩니다.

※ CVarArg는 포맷 문자열을 통해서 String을 초기화할 수 있기 때문에 id.uuidStringid as CVarArg 모두 uuidString을 나타내는 것으로 같은 의미의 코드입니다. (저는 간결한 코드를 위해 uuidString을 사용했습니다.)

삭제(Delete)

수정과 마찬가지로 UUID를 파라미터로 받아서 삭제하는 코드입니다.

static func deleteData(id: UUID) {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>.init(entityName: "Plan")
        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id.uuidString)

        do {
            guard let result = try? context.fetch(fetchRequest),
                  let object = result.first as? NSManagedObject else { return }
            context.delete(object)

            try context.save()
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }

전체 코드(CRUD)

import UIKit
import CoreData

final class CoreDataManager {
    
    private static let context: NSManagedObjectContext? = {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            print("AppDelegate가 초기화되지 않았습니다.")
            return nil
        }
        return appDelegate.persistentContainer.viewContext
    }()
    
    static func saveData() {
        guard let context = context else { return }
        guard let entity = NSEntityDescription.entity(
            forEntityName: "Plan", in: context
        ) else { return }
        
        let object = NSManagedObject(entity: entity, insertInto: context)
        object.setValue(UUID(), forKey: "uuid")
        object.setValue(Date.now, forKey: "date")
        object.setValue("Core Data 테스트", forKey: "content")
        object.setValue(true, forKey: "done")
        
        do {
            try context.save()
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }
    
    static func fetchData() {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Plan")
        
        do {
            guard let planList = try context.fetch(fetchRequest) as? [Plan] else { return }
            planList.forEach {
                print($0.uuid)
                print($0.date)
                print($0.content)
                print($0.done)
            }
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }
    
    static func updateData(id: UUID) {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>.init(entityName: "Plan")
//        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Plan")
        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id.uuidString)
//        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id as CVarArg)
        
        do {
            guard let result = try? context.fetch(fetchRequest),
                  let object = result.first as? NSManagedObject else { return }
            object.setValue("수정할 내용", forKey: "content")
            object.setValue(Date.now, forKey: "date")
            object.setValue(false, forKey: "done")
            
            try context.save()
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }
    
    static func deleteData(id: UUID) {
        guard let context = context else { return }
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>.init(entityName: "Plan")
        fetchRequest.predicate = NSPredicate(format: "uuid = %@", id.uuidString)

        do {
            guard let result = try? context.fetch(fetchRequest),
                  let object = result.first as? NSManagedObject else { return }
            context.delete(object)

            try context.save()
        } catch {
            print("error: \(error.localizedDescription)")
        }
    }
}
profile
 iOS Developer

2개의 댓글

comment-user-thumbnail
2023년 9월 15일

흥미로운데요? ;;;;

답글 달기
comment-user-thumbnail
2023년 9월 15일

이 게시글 보고 CoreData 완정정복...! 했습니다!

답글 달기