아주 기초적으로 Core Data를 사용한 Todo App을 만들어보려고 한다.
이론만 알고 넘어가기엔 머릿속에 안들어와서(...) 직접 해보면서 익혀보겠다!
일단 Core Data를 사용하려면 (1) 프로젝트 생성 시 Use Core Data를 클릭하는 방법과, (2) 기존 프로젝트에 Core Data - Data Model을 생성해주면 된다.
두가지 방법이 있지만, 처음부터 Core Data를 선택해서 프로젝트를 생성하는 것이 효율적일 것 같다. 기존 프로젝트에 추가하게 되면은 9할은 리팩토링이 필수적인...ㅠㅋㅋㅋㅋ
간단하게 장단점을 비교해보면 아래와 같다. 각자 진행하고 있는 프로젝트 특징(?)에 맞게 알아서 선택하시길^_^
Core Data 생성
Core Data 사용방법
1. 기존 프로젝트에 Core Data 추가
- 장점
- 기존 데이터와 Core Data 통합 가능
- 기존 프로젝트의 코드를 유지하면서 데이터베이스 지원 도입 가능
- 단점
- 기존 코드와의 호환성 문제나 데이터 마이그레이션 등의 복잡한 문제가 발생 가능성 높음
- 코드의 수정 및 리팩토링이 필요할 수 있음
2. 프로젝트 시작 시 Core Data 선택
- 장점
- 처음부터 데이터 모델을 확실히 구성할 수 있음
- 데이터 관리를 효율적으로 시작할 수 있음
- 단점
- 새로운 프로젝트를 시작하기 때문에 초기 설정이 필요하며, 추가 작업이 필요할 수 있음
- 기존 데이터가 없기 때문에 초기 데이터 입력이 필요할 수 있음
프로젝트 생성
나는 처음부터 Core Data를 선택해서 프로젝트를 생성하였다.
초기에 생성된 프로젝트 파일 목록을 보면 Core Data Model이 생성된 걸 볼 수 있다.
해당 Data Model에 Entities(Todo)를 추가하고 Attributes(todo, createDate, isComplete)를 추가해준다. 해당 프로퍼티에 맞는 타입도 지정해주면 된다.
Entities랑 Attributes 입력이 다 끝나면 NSManagedObject subclass를 생성해줘야 하는데, 그 전에 inspector에서 설정할 부분이 있다.
클래스를 설정하기 전에 Codegen을 선택해야 하는데 각 항목이 의미하는 바는 다음과 같다.
Codegen
Manual/None
- 엔터티와 관련된 클래스를 생성하지 않음
- 개발자가 직접 모든 코드를 작성해야 함
- 일반적으로 Manual/None로 설정할 때 사용자 정의 NSManagedObject 하위 클래스를 작성하고 엔터티를 관리함
Class Definition (기본값)
- NSManagedObject의 하위 클래스를 직접 작성해야 함
- Core Data는 자동으로 생성된 하위 클래스를 제공하지 않음
- 엔터티와 관련된 커스텀 NSManagedObject 클래스를 직접 작성해야 함
Category/Extension
- Xcode가 엔터티에 대한 확장(extension)을 생성해줌
- 이 확장을 사용하여 엔터티에 추가적인 속성, 메서드 또는 커스텀 로직을 추가할 수 있음
- 기존 NSManagedObject 클래스를 직접 확장하는 방식
Codegen을 선택한 후 Xcode의 Editor - Create NSManagedObject Subclass... 를 선택하여 하위 클래스를 생성해준다.
Todo+CoreDataClass.swift, Todo+CoreDataProperties.swift 두가지 파일이 생성됐다.
각 파일이 의미하는 바가 뭘까? 그리고 심지어 지금 CoreDataProperties 파일은 빨간줄 대잔치 🤮
NSManagedObject Subclass
Todo+CoreDataClass.swift
- "Todo" 엔터티의 관리 객체 관련 코드를 포함
- 관리 객체 클래스의 코드를 정의하며, 엔터티의 속성, 관계 및 기타 메타데이터를 포함
- 이 클래스는 직접 수정하지 않는 것이 좋음. 수정이 필요하다면 "Todo+CoreDataProperties.swift" 파일을 사용하여 사용자 지정 코드를 추가
Todo+CoreDataProperties.swift
- "Todo" 엔터티의 속성 및 관계에 대한 사용자 지정 코드를 추가하는 곳
- 속성에 대한 추가 로직, 계산 속성, 메서드 등을 작성 가능
- 이 파일을 통해 "Todo" 엔터티에 대한 사용자 지정 동작 및 로직을 구현할 수 있음
CoreDataProperties 파일의 에러 발생 이유는 이러하다.
나는 Codegen을 Extension으로 선택해서 Xcode가 알아서 확장을 생성해줬는데 Subclass로 중복 생성됐기 때문에 문제가 있다고 알려주는 것이다. 그럼 해당 파일을 지워주면 된다~!
객체를 얻는 방법
NSManagedObjectContext는 Core Data에서 데이터를 관리하고 조작하기 위한 핵심 클래스 중 하나이다. 이 클래스는 앱의 데이터 모델과 데이터베이스 간의 중간 역할을 수행하며, 데이터를 읽고 쓰고 변경하는 데 사용된다. NSManagedObjectContext를 사용하여 데이터의 생명주기를 관리하고 데이터의 일관성과 무결성을 유지한다.
주요 역할 및 속성
- 데이터 관리
- 데이터를 가져오고 저장하는 데 사용됨
- 데이터를 읽어오거나 추가, 수정, 삭제 가능
- 변경된 데이터는 임시로 저장되며, 데이터베이스와의 실제 저장은 save() 메서드를 호출할 때 이루어짐
- 변경 추적
- 데이터 변경 사항을 추적함
- 변경 사항은 관리 객체(managed objects)의 상태에 반영되며, 변경 이력을 유지
- 변경 사항을 저장하면 데이터베이스에 적용됨
- 병합
- 다중 스레드 환경에서 작업하는 경우, 데이터를 병합하고 동기화하는 데 사용됨
- 메인 스레드와 백그라운드 스레드 간의 데이터 일관성을 유지하는 데 도움을 줌
- 커밋
- save() 메서드를 호출하여 변경된 데이터를 커밋하고 데이터베이스에 저장할 수 있음
- 변경 내용이 롤백되거나 영구적으로 저장
- 데이터 관리 범위
- 특정 데이터베이스 연결과 연결된 데이터를 관리함
- 다수의 NSManagedObjectContext 인스턴스를 사용하여 다중 데이터베이스 연결을 다룰 수 있음
- 캐시
- 데이터를 캐시하여 데이터베이스 연산을 최적화함
- 캐시된 데이터는 읽기 작업을 가속화하고 중복된 쿼리를 방지함
- 상위 및 하위 컨텍스트
- 상위 및 하위 컨텍스트 구조를 지원함
- 상위 컨텍스트에서 변경 사항을 만들고 하위 컨텍스트에서 변경 사항을 커밋하여 다중 레벨의 컨텍스트를 구성할 수 있음
내 코드
class ViewController: UIViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
...
}
- UIApplication.shared
- iOS 애플리케이션의 공유 인스턴스를 나타냄
- 모든 iOS 애플리케이션에서 shared 프로퍼티를 통해 앱의 주요 객체에 접근할 수 있음
- .delegate
- 앱의 델리게이트(delegate) 객체에 접근함
- 앱의 델리게이트는 앱의 주요 동작을 관리하고 제어하는 객체로, 일반적으로 UIApplicationDelegate 프로토콜을 채택한 클래스
- as! AppDelegate
- 델리게이트 객체를 AppDelegate 클래스로 캐스팅함
- 이것은 델리게이트 객체가 실제로 AppDelegate 클래스의 인스턴스임을 확신하는 것
- .persistentContainer
- AppDelegate 클래스의 속성 또는 메서드 중 하나로, Core Data 스택(Core Data Stack)을 관리하는 객체인 NSPersistentContainer의 인스턴스를 반환함
- 이 객체는 Core Data 애플리케이션의 핵심 부분 중 하나이며, 데이터베이스 관리와 데이터 모델 로딩을 담당
- .viewContext
- NSManagedObjectContext 객체를 반환함
- NSManagedObjectContext는 Core Data에서 데이터를 읽고 쓰는 데 사용되며, 주로 앱의 사용자 인터페이스와 상호작용하고 데이터 저장소와 데이터 모델 사이의 중간 역할을 함
- viewContext는 주로 메인 스레드에서 사용되며, 데이터 모델과 상호작용하는데 사용됨
따라서 해당 코드는 앱의 AppDelegate를 통해 Core Data 스택에서 메인 스레드에서 사용할 수 있는 NSManagedObjectContext를 얻는 방법이다. 이 NSManagedObjectContext는 데이터를 검색하고 저장하는 데 사용된다.
데이터 가져오고 보여주기
NSFetchRequest는 Core Data에서 데이터를 검색하고 조회하기 위한 객체다. 이것은 데이터베이스에서 데이터를 쿼리하고 가져오는 데 사용되며, 검색 조건, 정렬 순서, 반환 유형 등을 지정할 수 있다. NSFetchRequest는 Core Data에서 데이터를 읽어오는 작업을 정의하며, 주로 NSManagedObjectContext와 같이 사용된다.
주요 속성 및 사용법
- entity
- 검색할 대상 엔터티를 지정
- 엔터티는 데이터 모델에서 정의한 데이터 유형(테이블)을 나타냄
(ex) let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
- predicate
- 검색 조건을 지정하는 프레디케이트(Predicate)를 설정
- 프레디케이트는 데이터 필터링에 사용됨
(ex) let predicate = NSPredicate(format: "age >= %@", argumentArray: [25])
- predicate를 설정하면 조건에 부합하는 데이터만 반환됨
- sortDescriptors
- 검색 결과를 정렬하는 데 사용
- 정렬 순서를 나타내는 NSSortDescriptor 객체 배열을 설정할 수 있음
(ex) let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
- sortDescriptors를 사용하면 결과를 원하는 순서로 정렬할 수 있음
- fetchLimit
- 최대 검색 결과 수를 제한할 수 있음
- 대량의 데이터를 일부만 검색할 수 있음
(ex) fetchRequest.fetchLimit = 10
- returnsObjectsAsFaults
- 기본적으로 검색 결과를 객체의 불러올(파울트) 상태로 가져옴
- 필요한 경우 객체를 실제로 로드하고 데이터를 채울 수 있음
(ex) fetchRequest.returnsObjectsAsFaults = false
- resultType
- 반환 유형 설정
- 기본값은 .managedObjectResultType, NSManagedObject 객체의 배열을 반환
- .countResultType (검색 결과 수를 반환) 및 .dictionaryResultType (딕셔너리 형태로 반환) 등이 있음
- propertiesToFetch
- 반환하려는 속성 지정
- 특정 열을 선택적으로 로드할 때 유용
- includesSubentities
- 하위 엔터티를 검색할지 여부를 설정
- 기본적으로 true로 설정되어 있어 하위 엔터티도 검색 대상에 포함됨
내 코드
override func viewDidLoad() {
super.viewDidLoad()
...
fetchTodo()
}
private func fetchTodo() {
let request = Todo.fetchRequest()
do {
self.todos = try context.fetch(request)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("fetchTodo Error")
}
}
- let request = Todo.fetchRequest()
- 데이터를 가져오기 위한 NSFetchRequest 객체를 생성
- Todo는 Core Data에서 정의한 엔터티로, fetchRequest() 메서드를 사용하여 해당 엔터티에 대한 검색 요청을 생성
- do { ... }
- 데이터를 가져오는 도중 발생할 수 있는 오류를 처리하기 위한 do-catch 구문
- self.todos = try context.fetch(request)
- 생성된 요청을 context (NSManagedObjectContext)에서 실행하여 데이터를 가져옴
- 가져온 데이터는 self.todos에 할당
- NSManagedObjectContext를 통해 데이터를 읽어오고 self.todos 배열에 할당하는 부분임
- DispatchQueue.main.async { ... }
- 가져온 데이터를 테이블 뷰에 표시하기 위해 메인 스레드에서 UI 업데이트를 수행하기 위한 비동기 블록을 생성
- UI 업데이트는 메인 스레드에서만 수행 가능
- self.tableView.reloadData()
- 테이블 뷰를 리로드하여 새로운 데이터로 화면 갱신
- 새로운 데이터가 테이블 뷰에 반영됨
- catch { ... }
- 데이터 검색 중에 오류가 발생하면 catch 블록이 실행되고, "fetchTodo Error" 메시지가 출력됨
📱 적용화면