-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
JEONG edited this page Aug 24, 2026
·
2 revisions
세 가지 에러 표현 방식과 선택 기준. 상황에 맞는 도구를 고르는 것이 핵심입니다. 구현 위치:
UMCApp/Core/Foundation/Sources/Error/(UMCFoundation모듈).
| 방식 | 용도 | 표현 |
|---|---|---|
| Loadable | 화면 내 인라인 상태 (리스트 로딩, 도메인 에러, 검증 실패) | 뷰 내부 상태 |
| ErrorHandler | 흐름 중단형 전역 에러 (세션 만료, 권한, 네트워크 오류) | 전역 Alert |
| AlertPrompt | 확인/취소 다이얼로그 (파괴적 작업, 분기점) | 모달 다이얼로그 |
Error/
├── Types/ AppError · NetworkError · RepositoryError · DomainError
│ AuthError · ValidationError · ErrorSeverity
├── Handler/ ErrorHandler · ErrorContext · PresentableError
├── Loadable/ Loadable
├── LocationError.swift
├── Error+Cancellation.swift // error.isCancellation
└── Error+TransportFailure.swift // error.isTransportFailure
public enum AppError: Error, LocalizedError, Equatable {
case repository(RepositoryError)
case network(NetworkError)
case validation(ValidationError)
case auth(AuthError)
case domain(DomainError)
case unknown(message: String)
}| 멤버 | 설명 |
|---|---|
errorDescription |
개발자용 상세 설명 (로깅) |
userMessage |
사용자에게 보여줄 문구 |
severity |
.info / .warning / .critical — Alert 제목 결정 |
isRetryable |
재시도 버튼 노출 여부 |
AppError.from(_:) |
임의 Error를 정규화. RepositoryError/NetworkError/AuthError를 각 케이스로 매핑, 그 외는 .unknown
|
catch블록마다 매핑 로직을 다시 쓰지 말고AppError.from(error)를 사용합니다.
| 타입 | 대표 케이스 |
|---|---|
NetworkError |
unauthorized, tokenRefreshFailed(reason:), noRefreshToken, requestFailed(statusCode:data:), invalidResponse, maxRetryExceeded, noNetwork, timeout
|
RepositoryError |
serverError(code:message:), decodingError(detail:), invalidResponse(detail:)
|
AuthError |
notLoggedIn, sessionExpired, invalidCredentials, socialLoginFailed(provider:reason:), accountSuspended(reason:), pendingApproval, rejected(reason:), notRegisteredMember, invalidVerificationCode, verificationCodeExpired
|
ValidationError |
empty(field:), invalidFormat(field:expected:), tooShort(field:minLength:), tooLong(field:maxLength:), invalidValue(field:reason:), mismatch(field1:field2:), alreadyInUse(field:)
|
DomainError |
공지·출석·워크북·미션·커리큘럼·게시글 규칙 위반 (attendanceOutOfRange, workbookDeadlinePassed, insufficientPermission(required:), custom(message:) 등) |
LocationError |
notAuthorized, locationFailed(_), timeout, geocodingFailed(_)
|
public enum Loadable<T: Equatable>: Equatable {
case idle // 초기 상태
case loading // 로딩 중
case loaded(T) // 성공
case failed(AppError) // 실패 (인라인 표시)
}편의 멤버: value · error · isLoading · isComplete · isIdle · map(_:)
switch viewModel.attendanceState {
case .idle: Color.clear.task { await viewModel.fetch() }
case .loading: LoadingView()
case .loaded(let data): AttendanceContentView(data: data)
case .failed(let error): RetryContentUnavailableView(error: error) { await viewModel.fetch() }
}@Observable
public final class ErrorHandler {
public private(set) var currentError: PresentableError?
public func handle(_ error: Error, context: ErrorContext)
public func clearError()
}errorHandler.handle(error, context: ErrorContext(
feature: "Activity",
action: "attendanceBtnTapped",
retryAction: { [weak self] in await self?.retry() }
))- 루트 뷰에
.globalErrorAlert(errorHandler: errorHandler)한 번만 부착합니다. -
handle은PresentableError를 만들어currentError에 싣고, Alert 제목은AppError.severity, 재시도 버튼은isRetryable+retryAction유무로 결정됩니다. -
취소는 에러가 아닙니다.
error.isCancellation인 경우 Alert를 띄우지 않습니다.
Loadable vs ErrorHandler
- ErrorHandler: 작업 흐름 중단, 즉각적 사용자 액션 필요 (세션 만료, 권한 요청, 네트워크 오류)
- Loadable: 화면 내 상태 표시 (리스트 로딩 실패, 도메인 에러, 검증 실패)
@Observable
final class SomeViewModel {
var alertPrompt: AlertPrompt?
func deleteButtonTapped() {
alertPrompt = AlertPrompt(
title: "삭제 확인",
message: "정말 삭제하시겠습니까?",
positiveBtnTitle: "삭제",
positiveBtnAction: { [weak self] in self?.delete() },
negativeBtnTitle: "취소",
isPositiveBtnDestructive: true
)
}
}
// View
.alertPrompt(item: $viewModel.alertPrompt)| 파라미터 | 설명 |
|---|---|
positiveBtnTitle / positiveBtnAction
|
긍정 버튼 (nil이면 미표시) |
secondaryBtnTitle / secondaryBtnAction
|
3번째 선택지가 필요할 때만 |
negativeBtnTitle / negativeBtnAction
|
취소 버튼 (보통 액션 없음) |
isPositiveBtnDestructive |
true면 긍정 버튼을 .destructive로 |
AlertPrompt 사용 기준
- 파괴적 작업 전 확인 (삭제, 초기화 등)
- 사용자 선택이 필요한 분기점
| 상황 | 타입 |
|---|---|
| 통신/토큰 재발급/타임아웃 | NetworkError |
| 서버 응답은 왔지만 비즈니스 실패/디코딩 실패 | RepositoryError |
| 로그인·가입·승인 상태 | AuthError |
| 입력값 검증 실패 | ValidationError |
| 도메인 규칙 위반 | DomainError |
| 화면 전달 타입 | AppError |
관련 문서: Architecture · Networking