"꼼꼼한 재은 씨의 Swift 실전편" 책의 요약 내용 입니다.
멀티 스레드, 멀티 유저를 지원하지 않는다.
단일 작업에서 처리성능을 향상시키기 위함.
멀티 스레드를 통해 코어 데이터를 사용해야 한다면 관리 객체와 컨텍스트의 사용에 주의해야 한다.
기존의 스레드가 데이터 저장을 끝내고난 후 다른 스레드가 새로운 컨텍스트 객체를 이용하는 식으로 접근해야 한다.
스키마 = 관리 객체 모델 = NSManagedObjectModel
클래스의 인스턴스
관리 객체 모델을 사용하여 영구 저장소의 레코드를 관리 객체에 연결할 수 있다.
즉, 레코드 각각을 관리 객체로 생성하여 다루는 구조.
엔터티(Entity) = 데이터가 저장될 구조, NSEntityDescroption
클래스로 표현
새로운 데이터 추가시 엔터티(NSEntityDescription)를 이용하여 관리 객체 모델(NSManagedObject) 객체가 만들어지며, 컨텍스트가 인식하여 관리하게 된다.
프로젝트가 만들어진 이후일 경우 프로젝트 탐색기에서 .xcdatamodeled 파일을 선택
코어 데이터 모델 편집창을 통해 Entity, Attribute, Relation 등을 정의
클래스명(관리 객체 모델명)은 통상 Entity명+MO
를 붙여 정의하는게 관례 (Model Object의 약자)
코어데이터의 Entity는 상속이 가능
상속 관계를 설정하려면 인스펙터의 Parent Entity
항목을 이용
예시: Person
Entity를 상속받은 Employee
Entity
Person
- bitthDate
- genter
- name
Employee
- empCd
- grade
- joinDate
- stateCd
Attribute: 데이터베이스에서의 칼럼, 또는 필드위 유사 개념
도메인(Domain) 값을 설정할 수 있다. (값의 범위 설정)
기본값(default value) 값을 설정할 수 있다. -> nil 값과는 다르게 처리하기 위한 용도로 사용
optional 타입으로 설정할 수 있다.
정규화된 데이터 모델의 연결이나 참조를 위해 사용된다.
릴레이션을 통해 다른 엔터티의 레코드를 함께 참조할 수 있다.
(join과 비슷)
관계형 데이터베이스의 외래키 칼럼과 같은 역할
코어데이터의 릴레이션의 경우 데이터베이스와 다르게 레코드를 직접 참조하는 방식
릴레이션에서 Type 값을 Entity
입장에서 참조할 Entity
로의 단방향 릴레이션
을 설정
Inverse
항목의 설정이 필요Relationship
@objc
: objective-c 기반의 코드에서도 인식할 수 있음. (attribute 값들이 objective-c 기반)
@NSManaged
: 해당 property가 동적으로 정의될 수 있음을 컴파일러에게 알리는 역할. (extension 내에서도 property 설정이 가능)
To One Relation
@objc(Problem_Core)
public class Problem_Core: NSManagedObject {
@NSManaged public var pid: Int64
@NSManaged public var pName: String?
@NSManaged public var contentImage: Data?
@NSManaged public var page: Page_Core? // 릴레이션에 의해 생성된 attribute
}
relation 된 entity 값은 problem.page.vid
식으로 접근할 수 있다.
To Many Relation
@objc(Page_Core)
public class Page_Core: NSManagedObject {
@NSManaged public var vid: Int64
@NSManaged public var materialImage: Data
@NSManaged public var layoutType: String
@NSManaged public var problems: NSSet? // 릴레이션에 의해 생성된 attribute
}
Array 형식이 아닌 NSSet 타입으로 정의 (동일 레코드의 중복 참조를 방지하기 위함)
순서를 보장하기 위해서는 Relationship 속성에서 Arrangement
값의 Ordered
값이 활성되어야 한다. (NSOrderedSet
타입이 된다.)
코어 데이터에서 사용되는 모든 모델 클래스는 NSManagedObject
의 하위 클래스.
커스텀 모델 클래스를 정의하기 위해서도 또한 NSManagedObject
의 하위 클래스여야 한다.
Xcode: Editor -> CreateNSManagedObjectSubclass... 를 통해 생성 가능.
Class 인스펙터
클래스명
(Entity명+MO) 런타임 오류가 발생
.Manual/None
값으로 설정컴파일 오류가 발생
.let container = NSPersistentContainer(name: "semomun")
프로젝트에 추가된 .xcdatamodeled
파일을 코어 데이터 시스템에 등록,
NSPresitentContainer 객체를 생성.
만약 .xcdatamodeled
파일명이 수정된다면 name 값 또한 수정되어야 한다.
let context = persistentContainer.viewContext // -> NSManagedObjectContext
코어 데이터에서 데이터를 읽고 쓰기 위한 컨텍스트 객체
func applicationWillTerminate(_ application: UIApplication) {
self.saveContext()
}
위 코드를 통해 앱이 종료될 때 안전하게 한번 더 저장할 수 있다.
ViewController
상에서 context
에 접근하는 예시 코드
func fetch() -> [NSManagedObject] {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Board")
let result = try! context.fetch(fetchRequest)
return result
}
위의 예시 코드처럼 NSManagedObject 타입으로 받은 경우 attribute 값을 접근하려면 .value(forKey: ) //Any 타입
식으로 접근해야 한다.
Any 타입으로 반환되기에 타입캐스팅을 통해 사용해야 한다.
코어 데이터에 저장된 데이터를 fetch하기 위한 요청 사항을 정의하기 위한 객체. (데이터베이스 상의 SELECT 와 유사 역할)
NSFetchRequest
NSPredicate
NSSortDescriptor
objective-c 표준 표현식을 따른다.
아래 세가지 값을 토대로 만들어진다.
let fetchRequest: NSFetchRequest<Problem_Core> = ProblemCore.fetchRequest()
let predicate = NSPredicate(format: "pid == %@, "\(pid)") // <검색조건 표현식>, <바인딩 변수>
fetchRequest.predicate = predicate
%@
, %K
, %d
등의 표현식 사용 가능.
Predicate Format String Syntax 에서 많은 정보를 알 수 있다.
%d
일 경우 <
, >
, <=
, >=
연산이 가능하다.%@
형식에 CVarArg
값일 경우 <
, >
, <=
, >=
연산이 가능하다.&&
를 통해 두가지 이상의 비교연산자를 사용할 수 있다.NSPredicate(format: "joindate >= %@ && joindate <= %@", startDate, endDate) // start ~ end 이내
CONTAINS
연산자를 통해 특정 String 값이 포함된 문자열을 검색할 수 있다.NSPredicate(format: "name CONTAINS %@", "길동") // "길동"이 포함된 name 모두 해당
NSSortDescriptor
객체를 생성하여 fetchRequest
객체의 정렬 속성에 대입.
let sort = NSSortDescropter(key: "date", ascendiing: false) // date 값을 내림차순으로 정렬 (최신순)
fetchRequest.sortDescropters = [sort] // 정렬 속성에 대입
속성이 2가지 이상일 경우 순서대로 앞 정렬값에 따라 정렬이 된 값을 토대로 다음 정렬이 이루어진다.
만약 새로 만들어진 object를 최신순으로 표시하고자 할때는 list.insert(object, at: 0)
식으로 넣는다.
또는 object를 context 상에 반영 후 save 한 다음 sort된 list를 받아오는 것도 방법이다.
context.save()
: Context 변경 사항을 영구저장소에 반영 (커밋, 동기화)context.rollBack()
: 모든 변경 내역을 원래대로 되돌리는 역할 (메모리상 변경 내역을 저장소와 동일시(제거))let object = NSEntityDescroption.insertNewObject(forEntityName: "Board", into: context) // 생성 후 context에 등록
object.setValue("title", forKey: "title") // 값 설정, 메모리에만 반영된 상태
context.save() // context 변경사항을 영구 저장소에 반영
제거가 되기 위해서는 context(메모리) 상에 인스턴스가 존재해야만 한다.
context.delete(object)
: context 내에서 제거context.save()
: 영구저장소에 반영Relationship 생성
커스텀 모델 클래스를 relation 내에 저장
@objc(Page_Core)
public class Page_Core: NSManagedObject {
@NSManaged public var vid: Int64
@NSManaged public var materialImage: Data
@NSManaged public var layoutType: String
@NSManaged public var problems: NSSet? // 릴레이션에 의해 생성된 attribute
@objc(addProblem:)
@NSManaged public func addToProblems(_ value: Problem_Core)
}
let problem = Problem_Core(context: context) // 커스텀 모델 생성
problem.setValues(problem: ProblemDTO) // 커스텀 모델 값 설정
section.addToProblems(problem) // 커스텀 모델 relation 내에 반영
1개 입장에서 pageCore.addToProblems(problem)
를 통해 등록해도 되고,
n개 입장에서 problem.page = pageCore
식으로 등록해도 같은 결과를 가져옵니다.
이는 서로 역참조 관계이기 때문.
let problems = pageCore.problems?.array as! [Problem_Core]
식의 코드를 통해 relation에 연결되어 있는 커스텀 모델 클래스들을 배열로 접근할 수 있습니다.
.array
속성을 통해 NSSet
or NSOrderedSet
-> Array
로 반환합니다.
중요한 점은 problems 는 "참조" 정보들(메모리 주소들)이며, 실제 데이터를 담고 있지는 않다.
따라서 크기가 그만큼 커지지는 않는다.
let fetchRequest: NSFetchRequest<Problem_Core> = ProblemCore.fetchRequest()
let predict = NSPredicate(format: "page == %@", pageCore)
fetchRequest.predicate = predict
let sort = NSSortDescripter(key: "pid", ascending: true) // 만약 pid 값으로 오름차순이 필요하다면
fetchRequest.sortDescriptors = [sort]
context.fetch(fetchRequest) // fetch 실행
위 코드를 사용해도 동일하게 불러올 수 있습니다.
objectID: NSManagedObjectID
object.objectID
값을 통해 특정 커스텀 모델의 identifier 값을 알 수 있습니다.
context.object(with: objectID)
메소드를 통해 특정 커스텀 모델을 접근할 수 있습니다.
단, 데이터베이스 상의 PK 값과는 다르게 context 값이 수정될 때 마다 변경될 수 있으므로 그때그때 context 에서 참조해서 사용하는 것이 안전합니다.