Dex (1)
이번엔 CoreData를 사용하는 프로젝트이다.
물론 이전에 나름 열심히 글을 작성했던 적이 있다.
다시 한번 읽어보면 좋을듯
다시 돌아와서 뭐 알겠지만 프로젝트를 생성할때
이렇게 Storage에서 미리 선택을 하여 프로젝트 생성이 가능하다.
그러면 기본적으로 코드가 자동으로 생성이 된채로 프로젝트가 만들어지게 된다.
그리고 이번엔 Swift6로 버전업을 하고 코드를 작성한다.
이후 필요한 이미지들을 Assets에 추가해준다.
그리고 api는 Pokeapi 이걸 사용한다.
Entity 만들기
기본적으로 알고 있는
Entity 및 Atrributes 만들기 이다.
Json을 보고 필요한 것들을 이렇게 만들어 준다.
이때 눈여겨 봐야할 것이 바로 types이다.
types는 배열이므로 배열을 하고싶지만 그게 없기때문에 Transformable로 한다.
이것만 Type이 여태껏 보지못했던 Transformable
이다.
우선 Docs도 있으니 한번 읽어볼 것
🔄 Transformable Attribute 정리 (Core Data)
✅ 개요
- Transformable Attribute는 비표준 타입의 객체를 저장할 때 사용된다.
- 예:
UIColor
,UIImage
,Array
,Dictionary
, 또는 사용자 정의 클래스 등. - Xcode의 Core Data Model Inspector에서
Attribute Type
을Transformable
로 설정하면 사용 가능하다.
⚙️ 설정 방법
- Core Data 모델에서 해당 속성의
Attribute Type
을Transformable
로 설정 Custom Class
필드에 저장할 타입을 입력 (예:UIColor
)Value Transformer
에 사용할 transformer의 이름을 입력
🛠 Transformer 등록
- Core Data를 로드하기 이전에, 반드시 transformer를 등록해야 함
ValueTransformer.setValueTransformer(_:forName:)
메서드를 사용하여 등록- 등록 시 사용하는 이름은
NSValueTransformerName
의 extension에서 정의 가능
📌 Transformer 클래스 요구사항
- Transformer는
NSSecureUnarchiveFromDataTransformer
의 서브클래스이어야 한다. - 필수 메서드:
transformedValue(_:)
:Data
→ 사용자 정의 객체reverseTransformedValue(_:)
: 사용자 정의 객체 →Data
📋 요약
항목 | 설명 |
---|---|
용도 | Core Data에서 UIColor , Custom Struct/Class 등 비표준 타입 저장 시 사용 |
설정 | Data Model Inspector에서 Transformable 로 설정, 클래스와 트랜스포머 명 지정 |
등록 위치 | Core Data Stack 로드 전에 Transformer 등록 필수 |
변환 방향 | 양방향 (Data ↔ 사용자 정의 타입) 변환 가능해야 함 |
이상 Docs에서 가져온 내용을 정리한 것.
다시 돌아와서
Transformable로 하게되면, 이런식으로 Warning이 발생
경고 메시지를 통해 트랜스포머를 어떻게 설정해야 할지 힌트를 제공한다.
이렇게 Transformer에 NSSecureUnarchiveFromData
라고 적어주자.
그리고 그아래 Custom Class에 우리가 설정하고자하는 타입을 적는다.
우리는 String Array가 필요하므로 [String]
이라고 해주면 된다.
그리고
이렇게 constraints를 추가해주었다.
🔒 Core Data의 Constraints 정리 (with SQL 개념 비유)
✅ Constraints란?
- Core Data의 Constraints는 특정 속성에 대해 중복을 방지하고 고유한 데이터 집합을 유지하는 기능이다.
- 예:
email
,username
등 사용자마다 유일해야 하는 속성에 사용된다. - 🧩 비유: SQL의
UNIQUE
또는PRIMARY KEY
와 유사
🧩 왜 사용하나?
- 기존에는 중복 데이터를 방지하려면:
- Fetch → 존재 여부 확인 → 조건 분기 후 삽입/업데이트
- Constraints를 사용하면 추가 로직 없이 자동으로 중복 방지 가능
- 성능 향상: fetch 없이도 중복 여부 판단 가능
- 코드 간결화: 조건 분기 없이도 Core Data가 자동 감지
- 🧩 비유: SQL에서
INSERT ... ON CONFLICT
같은 동작을 자동으로 수행
⚙️ 설정 방법
- Xcode의 Data Model에서 엔티티 선택 후,
- Constraints 필드에 고유하게 만들 속성 이름을 쉼표로 구분하여 입력
- 예:
email
,username
- 🧩 비유: SQL의
UNIQUE(name, email)
제약을 모델에 시각적으로 설정
🧮 SQL과의 개념 비교
Core Data Constraints | SQL 개념 | 설명 |
---|---|---|
Unique Constraint | UNIQUE | 특정 속성의 중복을 허용하지 않음 |
고유 속성 조합 지정 가능 | UNIQUE (col1, col2) | 복합 유니크 제약 가능 |
내부 objectID 존재 | PRIMARY KEY (id) | Core Data는 내부적으로 UUID 기반의 식별자 사용 |
충돌 시 merge 처리 가능 | ON CONFLICT 절 등 | SQL에서는 충돌 시 명시적 에러, Core Data는 정책 선택 가능 |
⚠️ 저장 실패와 충돌 처리
- 중복된 값이 constraints를 위반할 경우, 저장 시 오류 발생
- 예: 중복된
name
이 있는 경우,save()
시 에러 발생 - 🧩 비유: SQL에서
INSERT
시 PRIMARY KEY 중복이면 에러 나는 것과 동일
🔄 Merge Policy 설정
- 충돌 시 자동 병합 처리 방법을 정할 수 있음
mergePolicy
속성을 통해 충돌 해결 방식 지정 가능
주요 Merge Policy 예시
NSMergeByPropertyObjectTrumpMergePolicy
: 메모리 값이 저장소 값을 덮어씀NSMergeByPropertyStoreTrumpMergePolicy
: 저장소 값이 메모리 값을 유지함- 🧩 비유: SQL의
MERGE
혹은UPSERT
동작에 대한 처리 전략
🎯 결과
- Constraints와 Merge Policy를 적절히 설정하면,
- 중복 데이터 없이 고유 엔티티 관리 가능
- 중복 검사 로직 없이도 일관성 있는 데이터 유지
- 성능 향상 + 코드 간소화 효과
- 🧩 비유: 제약조건과 병합 정책으로 SQL에서
UNIQUE + UPSERT
로직을 자동화한 것과 유사
📌 요약
항목 | 설명 |
---|---|
목적 | 중복 방지, 고유성 보장 |
설정 위치 | Xcode Data Model > Entity > Constraints |
SQL 유사 개념 | UNIQUE , PRIMARY KEY |
효과 | fetch 없이도 중복 검사 가능, 코드 간결 |
주의사항 | 저장 충돌 시 Merge Policy 설정 필요 |
해당 내용은 여기의 내용을 GPT를 통해 정리하면서 내가 알고있던 SQL의 Primary Key의 개념을 믹스.
돌아와서 favorite의 경우 default Value를 false로 설정한다.
이미지 참고.
❓ Core Data에서의 Optional 정리
✅ Core Data의 Optional은 Swift의 Optional과 다르다
- Core Data의 “Optional”: 값이 저장될 때(nil이 아니어야 함) 유효
- Swift의 Optional: 객체가 초기화된 후 언제든지 nil이 될 수 있음
🧩 혼동 포인트: Xcode 모델에서 Optional 체크 해제했는데도?
- Xcode에서
Optional
체크를 해제해도, Xcode가 생성한 코드는 여전히 SwiftOptional
(Date?
)로 나옴 - 이유: Swift는 컴파일 타임에 옵셔널 여부를 강하게 요구하고, Core Data는 런타임 저장 시점만 검사하기 때문
- 👉 Core Data와 Swift는 옵셔널에 대한 규칙이 다르며, 서로 알지 못함
🧨 옵셔널을 강제로 제거하면 생기는 문제
@NSManaged var timestamp: Date
처럼 옵셔널을 제거할 수 있음 (Xcode는 허용)- 그러나 Swift는 이 속성이 항상 초기화되어 있다고 믿고, nil일 가능성을 고려하지 않음
- Core Data는
save()
시점 전까지는 값이 nil이어도 괜찮음 → 둘의 규칙이 충돌 - 만약 값을 주지 않고 접근하면 앱은 런타임 크래시
☑️ 방지 방법
1. Default Value 설정
Xcode 모델 편집기에서 기본값 지정 → 객체 생성 시 자동 값 부여됨2. awakeFromInsert 오버라이드
객체 생성 직후 값을 직접 설정 가능
예:timestamp = Date()
설정3. 옵셔널 유지하고, nil 체크 후 사용
가장 안전하지만 코드가 장황해질 수 있음
⚠️ 요약
구분 | Core Data Optional | Swift Optional |
---|---|---|
의미 | 저장 시 nil 허용 여부 | 어떤 시점에서든 nil 가능 |
검사 시점 | 런타임 저장 시 | 컴파일 시점부터 |
값이 없을 때 | 저장 불가 (Crash) | 사용 전 nil 체크 가능 |
권장 방식 | Swift 코드에서는 옵셔널 유지 | or 초기값 지정 필수 |
이내용 역시 여기서 GPT를 통해 정리.
여기서는 Attributes를 전체 선택한 뒤 Optional 체크를 해제하여 모든 속성이 저장 시 nil을 허용하지 않도록 설정하였다.
즉, Core Data의 Attributes에서 Optional을 체크 해제 했다는건
- 해당 속성은 반드시 값이 있어야 하며, nil이면 안 된다는 의미
- 이는 Core Data 레벨에서의 제약 조건이며, 저장 시점(
save()
)에 적용된다.
항목 | 설명 |
---|---|
Optional 체크 해제 | Core Data 저장 시 해당 속성은 nil이면 안 됨 |
Swift 코드상 선언 | 여전히 Optional(?)일 수 있음 |
위험 요소 | 값 없이 저장하면 런타임 크래시 발생 |
해결 방안 | 기본값 설정, awakeFromInsert에서 초기화, nil 체크 등 |
정리 🔍 Core Data Optional vs Swift Optional
✅ Swift Optional이란?
- Swift에서 Optional은 특정 속성에 값이 있을 수도, 없을 수도 있음(nil) 을 의미
- 컴파일러가 엄격히 검사하며, 사용 전 nil 체크가 필요
- 예:
String?
,Int?
등
✅ Core Data Optional이란?
- Core Data의 모델 에디터에 있는 “Optional” 체크박스는 → 해당 속성이 저장될 때 값이 없어도 되는지 여부를 의미
- 즉, “Optional” 체크 시 → 값 없이도 저장 가능
- “Optional” 체크 해제 시 → 저장 전 반드시 값이 있어야 함
🧠 Swift와 Core Data의 Optional은 다르다
- Core Data의 Optional 설정은 런타임 저장 시점에서만 검사됨
- Swift의 Optional은 컴파일 타임부터 엄격히 검사
- 따라서 둘은 완전히 별개의 개념이며, 혼동해서는 안 됨
⚠️ 혼동 포인트
- Core Data 모델에서 “Optional”을 체크 해제해도, → Swift 코드에서는 여전히
?
가 붙은 Optional로 생성되는 경우가 있음 - 이유: Apple이 내부적으로 특정 타입을 Swift Optional로 강제하기 때문
- 대표적으로
String
,URL
타입은 Core Data에서 non-optional이어도 Swift에서는 Optional로 생성됨
🛠 실무에서 주의할 점
- Swift에서는 Optional이기 때문에 사용 전 nil 체크가 필요
- Core Data 저장 시에는 Optional 체크 해제 속성은 값이 없으면 저장 실패로 이어짐
- 즉, 저장 전에 모든 필수 속성에 값을 지정했는지 확인해야 함
🧩 핵심 정리
- Core Data Optional: “값이 없어도 저장 가능한가?” → 런타임 검사
- Swift Optional: “값이 없을 수 있는가?” → 컴파일 타임부터 검사
- 둘은 관련 없어 보이지만 함께 작동하며, 타입에 따라 Swift Optional로 고정되는 경우도 존재
PersistenceController 수정하기
이제 본격적으로 진행을 해보려 한다.
기존에 있던 newItem 대신 newPokemon으로 만든다.
Item Entity가 지워져서 에러가 발생하는데 이건 무시하고 먼저 Controller부터 해결
이렇게 Entity 를 못찾을땐 XCode를 껐다 다시 켜먼 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let newPokemon = Pokemon(context: viewContext)
newPokemon.id = 1
newPokemon.name = "bulbasaur"
newPokemon.types = ["grass", "poison"]
newPokemon.hp = 45
newPokemon.attack = 49
newPokemon.defense = 49
newPokemon.specialAttack = 65
newPokemon.specialDefense = 65
newPokemon.speed = 45
newPokemon.sprite = URL(string:
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png")
newPokemon.shiny = URL(string:
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png")
이렇게 초기값을 하나 만들어 주었다.
이때 한가지 눈여겨봐야 할 것은
Coredata로 부터 가져온 Attributes들이 Swift가 어떻게 처리하는지 이다.
간단하게 표로 정리하면
타입 | Swift Optional 여부 |
---|---|
Int | ❌ (Optional 아님) |
String | ✅ (String? ) |
[String] | ✅ ([String]? ) |
URL | ✅ (URL? ) |
Int는 값이 없을 때 보통 0으로 처리하지만, String은 빈 문자열과 nil을 구분해야 하므로 Swift가 타입에 따라 Optional 여부를 결정한 것처럼 보인다.
이번글은 꽤나 내용이 길기에 최대한 이해를 하고 넘어가야 하는것이 포인트!