본문 바로가기
감정일기(가칭)

🍋 감정일기 앱의 Core Data 설계 — 왜 이미지 엔티티를 분리했는가?

by 밤새는 탐험가89 2025. 10. 16.
728x90
SMALL

https://explorer89.tistory.com/486

 

📸 Core Data에서 ‘이미지 피드 + 게시글 연결’ 기능을 설계하는 방법

“저장된 이미지를 한눈에 보여주고,이미지를 누르면 해당 일기(게시글)로 이동하게 만들고 싶어요!” 많은 앱들이 이런 UI를 가지고 있습니다.대표적으로 인스타그램, 네이버 블로그, 다이어리

explorer89.tistory.com

 

앱을 설계할 때 가장 중요한 건 데이터 구조를 어떻게 정의하느냐다.


이번 포스트에서는 나의 감정일기 앱에서
Core Data 모델을 어떤 기준으로 설계했는지, 그리고
“이미지 데이터를 왜 별도의 엔티티로 분리했는지”를 정리해보려 한다.


🧩 1️⃣ 기본 구조 개요

감정일기 앱은 사용자가 하루의 감정을 기록하고,
그날의 이미지를 함께 첨부하는 구조다.

 

이때 데이터를 관리하기 위해 Core Data를 사용했고,
두 개의 엔티티(Entity)를 설계했다.

엔티티 역할
EmotionDiaryEntity 감정일기의 기본 데이터 (감정, 내용, 날짜 등)
DiaryImageEntity 첨부된 이미지의 정보 (파일 경로, 생성일 등)

🧱 2️⃣ 감정일기와 이미지의 관계: 1 : N

한 개의 감정일기에는 여러 장의 이미지를 저장할 수 있어야 한다.
그래서 두 엔티티는 다음과 같은 관계를 갖는다 👇

EmotionDiaryEntity (1)
 └──> images (To-Many)
        ▼
DiaryImageEntity (N)
 └──> diary (To-One)

이렇게 설계함으로써,

하나의 일기에 여러 이미지를 연결할 수 있고

반대로 특정 이미지가 어떤 일기에 속하는지도 쉽게 알 수 있다.

 

즉, Core Data가 제공하는 양방향 관계(Inverse Relationship) 를 활용해
데이터 일관성을 유지할 수 있다.


🔄 3️⃣ Inverse Relationship이란?

Core Data에서 관계를 설정할 때는 “inverse”를 반드시 지정해야 한다.
이 inverse는 관계의 양방향 동기화 역할을 한다.

관계 Inverse
EmotionDiaryEntity.images DiaryImageEntity.diary
DiaryImageEntity.diary EmotionDiaryEntity.images

 

이 설정 덕분에 Core Data는
“이 이미지가 어떤 일기에 속하는지”를 자동으로 추적할 수 있다.

 

예를 들어 아래 코드를 보면 👇

diary.images.insert(imageEntity)

 

이 코드 한 줄만으로 Core Data는 내부적으로

imageEntity.diary = diary

 

까지 자동으로 동기화한다.

 

즉, 양방향 데이터 연결이 보장되어 일관성이 깨지지 않는다.


💡 4️⃣ 이미지 데이터를 별도의 엔티티로 분리한 이유

많은 초보자들이 흔히 이렇게 설계하곤 한다 👇

EmotionDiaryEntity
 └── images: [String] (Transformable)

 

즉, 여러 이미지 경로를 하나의 배열로 묶어서 저장하는 방식이다.
하지만 이 방식에는 다음과 같은 문제가 있다.

문제점 설명
⚠️ 확장성 부족 나중에 이미지에 “위치 정보”나 “태그”를 추가하기 어려움
⚠️ 수정 비효율 배열 내 하나의 이미지만 수정해도 전체 배열을 다시 저장해야 함
⚠️ 검색 불가 Core Data는 Transformable 타입 내부를 인식하지 못함
⚠️ Cascade 미지원 일기 삭제 시 개별 이미지 자동 삭제 불가

 

따라서 나는 이미지 데이터를 별도의 DiaryImageEntity로 분리했다.

 

이렇게 하면

1. 각 이미지가 독립적으로 관리되고

2. Core Data의 Cascade Delete Rule을 활용해 일기 삭제 시 자동 정리되며

3. 나중에 이미지별로 위치 정보(latitude, longitude)나 태그 등을 확장하기 쉬워진다.

 

확장성, 관리 효율성, 검색 성능 모두 확보되는 구조다.


🧠 5️⃣ id는 왜 String 타입으로 설정했는가?

Core Data에는 고유 식별자로 UUID 타입을 많이 사용한다.
하지만 나는 String 타입을 선택했다.

그 이유는 다음과 같다 👇

이유 설명
✅ FileManager와의 호환성 이미지 파일명을 직접 id와 동일하게 쓸 수 있음
✅ 외부 연동 용이 CloudKit, Firebase 등과의 동기화 시 변환 불필요
✅ 디버깅 편의성 id 값을 바로 로그로 확인 가능

 

예를 들어,
이미지 파일명을 "감정일기id_image_1.jpg" 형태로 저장하면,
해당 파일명과 Core Data의 image id가 완전히 일치하게 된다.

imageEntity.id = "\(diary.id)_image_\(index)"
imageEntity.imagePath = FileManagerHelper.save(image, id: imageEntity.id)

 

이렇게 하면 파일과 데이터베이스의 관계가 명확해지고,
디버깅이나 데이터 정리 시에도 혼동이 없다.


⚙️ 6️⃣ Delete Rule 설정

관계 Delete Rule  설명
EmotionDiaryEntity → images Cascade 일기를 삭제하면 연결된 이미지도 자동 삭제
DiaryImageEntity → diary Nullify 이미지를 개별 삭제해도 부모 일기에는 영향 없음

 

이 조합은 Core Data에서 가장 안정적인 형태다.

 

✅ 부모 삭제 시 자식 자동 정리,
✅ 자식 단독 삭제 시 안전한 null 처리.


🧩 7️⃣ 최종 Core Data 모델 요약

Entity 주요 속성  관계
EmotionDiaryEntity id (String), emotion (String), content (String), createdAt (Date) images (To-Many, Cascade)
DiaryImageEntity id (String), imagePath (String), createdAt (Date) diary (To-One, Nullify)
EmotionDiaryEntity
 ├── id: String
 ├── emotion: String
 ├── content: String
 ├── createdAt: Date
 └── images (To-Many) ──▶ DiaryImageEntity
                           ├── id: String
                           ├── imagePath: String
                           ├── createdAt: Date
                           └── diary (To-One)

 

 


🚀 8️⃣ 결론

이번 Core Data 설계의 핵심은 단순하다.

 

“이미지를 배열로 묶지 말고, 개별 Entity로 관리하자.”

 

이 결정 하나로

 

1. 데이터 정합성

2.삭제 규칙의 자동화 (Cascade)

3. 미래 확장성 (위치, 태그 추가)

4. FileManager와의 구조적 일관성

 

을 모두 확보했다.

 

이 구조는 추후

 

1. 위치 기반 일기 정렬,

2. AI 감정 분석 기능,

3. 이미지 갤러리 화면 구현으로 확장하기에도 가장 안정적인 기반이 된다.

 

728x90
LIST