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

⚙️ Core Data에서 catch가 작동하지 않는 이유

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

✅ saveContext() 리팩터링 이야기

Core Data를 다루다 보면 이렇게 do-catch 구문을 자주 쓰게 됩니다.

do {
    saveContext()
    return true
} catch {
    print("❌ 저장 실패:", error)
    return false
}

 

그런데 Swift 컴파일러가 아래와 같은 경고를 띄운다면,
당신도 같은 함정에 빠진 거예요 👇

 

⚠️ 'catch' block is unreachable because no errors are thrown in 'do' block'
(catch 블록은 실행될 수 없습니다. do 블록 안에서 오류를 던지는 코드가 없습니다.)


🧩 문제 상황

나의 기존 saveContext() 구현은 다음과 같았어요.

func saveContext() {
    guard context.hasChanges else { return }
    
    do {
        try context.save()
        LogManager.print(.success, "Core Data 저장 성공")
    } catch {
        LogManager.print(.error, "Core Data 저장 실패: \(error.localizedDescription)")
    }
}

 

그리고 이 메서드를 saveDiary() 안에서 이렇게 썼죠 👇

do {
    saveContext()
    return true
} catch {
    LogManager.print(.error, "감정일기 저장 실패: \(error.localizedDescription)")
    return false
}

 

그런데 Swift 컴파일러는
“이 catch 블록은 절대 실행되지 않는다”고 알려줬어요.
왜일까요?


🔍 원인 분석 — “throw”가 없는데 “catch”를 붙인 구조

사실 이 문제는 Swift 언어 레벨의 아주 단순한 원리예요.

 

1. catch 블록은 오직 throw로 던져진 에러만 잡을 수 있음

2. 그런데 saveContext()는 throws 함수가 아님 (func saveContext())

 

즉, saveContext() 내부에서는 이미 try-catch로 에러를 처리했기 때문에
외부에서는 더 이상 throw되는 오류가 존재하지 않아요.

 

그래서 컴파일러는 이렇게 판단하죠 👇

 

“do 안에서 에러를 던지는 코드가 없으니까 catch는 무의미하네?”

 

결국 catch는 절대 도달할 수 없는 코드(unreachable code) 로 취급됩니다.


⚒️ 해결 방향

문제는 명확했어요.
“저장이 실패했는지 성공했는지를 상위에서 알 수 있어야 한다.”

 

하지만 단순히 throws로 던지기에는 구조가 복잡해지기 때문에
saveContext()를 Bool 타입 반환으로 바꾸는 게 가장 현실적이고 깔끔한 해법이었죠.


✅ 수정된 saveContext() (Bool 반환형)

@discardableResult
func saveContext() -> Bool {
    guard context.hasChanges else {
        LogManager.print(.warning, "저장할 변경사항이 없습니다.")
        return false
    }
    
    do {
        try context.save()
        LogManager.print(.success, "Core Data 저장 성공")
        return true
    } catch {
        LogManager.print(.error, "Core Data 저장 실패: \(error.localizedDescription)")
        context.rollback()
        return false
    }
}

 

이제 saveContext()는
성공하면 true, 실패하면 false를 반환합니다.


✅ 수정된 saveDiary() (결과 기반 반환)

@discardableResult
func saveDiary(_ model: EmotionDiaryModel) -> Bool {
    let diary = EmotionDiaryEntity(context: context)
    diary.id = model.id.uuidString
    diary.emotion = model.emotion
    diary.content = model.content
    diary.createdAt = model.createdAt
    
    // 이미지 저장
    if let images = model.images {
        for (index, image) in images.enumerated() {
            if let path = DiaryImageFileManager.shared.saveImage(image, diaryID: model.id.uuidString, index: index) {
                let imageEntity = DiaryImageEntity(context: context)
                imageEntity.id = UUID().uuidString
                imageEntity.imagePath = path
                imageEntity.diary = diary
                diary.addToImages(imageEntity)
            }
        }
    }
    
    // ✅ 저장 결과 반영
    let success = saveContext()
    if success {
        LogManager.print(.success, "감정일기 저장 성공 (\(model.id))")
    } else {
        LogManager.print(.error, "감정일기 저장 실패 (\(model.id))")
    }
    return success
}

 

이제 테스트 코드에서도 이렇게 간단히 결과를 확인할 수 있죠 👇

func testSaveDiary() {
    let dummy = EmotionDiaryModel(...)
    let success = coreDataManager.saveDiary(dummy)
    XCTAssertTrue(success, "감정일기 저장 실패!")
}

💡 이렇게 바꿨을 때의 장점

항목 개선 전 개선 후
에러 처리 위치 내부에서 처리만 하고 외부 알림 없음 상위에서 성공/실패 인식 가능
테스트 용이성 저장 성공 여부를 확인할 방법 없음 ✅ Bool 기반으로 바로 검증 가능
코드 가독성 불필요한 do-catch 중복 ✅ 단일 책임, 간결한 로직
확장성 비동기/네트워크 연동 시 어려움 결과 기반 로직으로 전환 쉬움

✨ 결론

“catch가 실행되지 않는 건 Swift의 문제가 아니라,
에러가 던져지지 않는 구조 때문입니다.”

 

1. throws가 없는 함수에서는 catch가 동작하지 않습니다.

2. Core Data의 save()처럼 실패 가능성이 있는 동작은
내부에서 Bool로 반환하거나 throws를 명시해야 합니다.

3. 실제 앱에서는 Bool 반환이 단순하고 안정적인 접근입니다.


📘 정리 한 줄 요약

💡 “Core Data는 try-catch보다 결과 반환형(Bool)이 더 실용적이다.”

내부에서 do-catch로 에러를 삼켜버리면 상위 레벨에서는 성공/실패를 알 수 없습니다.
그래서 saveContext()는 에러를 던지지 말고, 결과를 반환하는 구조로 바꿔야 합니다 ✅

728x90
LIST