Error Handling이란 한국어로 에러 처리라고 번역할 수 있습니다.
즉, 말 그대로 해석하면
프로그램 실행 중 발생할 수 있는 오류를 감지하고, 이를 적절히 처리하여 프로그램이 예기치 않게 종료되지 않도록 하는 중요한 기법입니다.
파일을 읽어올 수 없거나 네트워크 요청 실패, 사용자 입력 오류와 같은 여러 가지 사용자에게 발생할 수 있습니다. 이러한 상황을 감지하고, 이를 적절하게 처리하는 방법을 알아보겠습니다.
에러가 발생하는 상황에는 정말 여러 가지가 존재하지만 위에서 언급한 세 가지만 알아보겠습니다.
파일 읽기 실패
파일을 읽는 과정에서 발생할 수 있는 오류는 파일이 존재하지 않거나 읽기 권한 부족, 파일 손상 또는 접근 불가와 같은 상화에서 주로 발생합니다.
Apple 공식 문서에 따르면, Swift에서는 주로 do-catch 구문을 사용하여 에러를 처리합니다.
예를 들어, throws 키워드를 사용하여 에러를 던질 수 있음을 명시한 후 호출하는 측에서 do-catch 구문을 통해 에러를 처리하면 됩니다.
이론만 보면 이해가 어려우니 코드를 통해 에러 처리를 해보겠습니다.
다음은 파일 읽기 과정에서 발생할 수 있는 다양한 오류를 감지하고, 상황에 맞는 메시지를 제공하여 안전하게 처리하는 Swift의 에러 처리를 구현 한 코드입니다.
import Foundation
// 파일 읽기 함수 정의
func readFileContents(at path: String) throws -> String {
let fileURL = URL(fileURLWithPath: path)
// 파일 내용을 읽고 반환 (에러가 발생할 수 있음)
return try String(contentsOf: fileURL, encoding: .utf8)
}
// 파일 읽기와 에러 처리
func processFile() {
let filePath = "/path/to/your/file.txt" // 읽으려는 파일 경로 설정
do {
// 파일 읽기를 시도
let content = try readFileContents(at: filePath)
print("파일 내용: \(content)")
} catch let error as NSError {
// 발생한 에러를 처리
switch error.code {
case NSFileReadNoSuchFileError:
print("에러: 파일이 존재하지 않습니다.")
case NSFileReadNoPermissionError:
print("에러: 파일을 읽을 권한이 없습니다.")
default:
print("에러: 알 수 없는 문제가 발생했습니다: \(error.localizedDescription)")
}
}
}
// 함수 실행
processFile()
위 코드를 하나하나 뜯어보겠습니다.
1. readFileContents 함수
func readFileContents(at path: String) throws -> String {
let fileURL = URL(fileURLWithPath: path)
return try String(contentsOf: fileURL, encoding: .utf8)
}
파일 읽기
먼저 reaFileContents 함수의 역할은 파일을 읽어옵니다. 파일 경로 path를 입력받아 해당 파일의 내용을 읽어 반환하게 됩니다.
반환 타입은 Stirng이며, 파일의 내용이 문자열로 읽힙니다.
주요 포인트
A. URL(fileURLWithPath:)
fileURL은 파일 경로를 나타내는 객체입니다. Swift에서 파일 작업은 대부분 URL 객체를 통해 이루어집니다.
B. String(contentsOf:encoding:):
파일 내용을 읽어올 때 사용하는 메서드입니다.
contentsOf: 는 URL 객체를 입력받아 파일 내용을 읽어오며, encoding:은 파일의 문자 인코딩 방식을 지정합니다.
C. throws
파일 읽기 중 문제가 발생할 가능성을 포함하고 있습니다. (예: 파일이 없거나 권한 부족)
throws 키워드를 통해 에러가 발생했을 때 호출한 쪽에서 이를 처리하도록 전달할 수 있습니다.
하지만 이 함수는 에러를 처리하는 것이 아닌 말 그대로 발생한 에러를 "던지는" 역할만 합니다.
D. try
String(contentsOf:) 메서드는 에러를 발생시킬 수 있으므로, 이를 호출할 때 try를 사용해야 합니다.
try는 throws 함수가 에러를 던질 수 있다는 것을 나타냅니다. 호출하는 쪽에서 이를 인지하고 처리할 준비가 되어있음을 의미합니다.
에러를 처리하지 않고 try 없이 호출하면 당연히 컴파일 에러가 발생합니다.
2. processFile 함수
func processFile() {
let filePath = "/path/to/your/file.txt"
do {
let content = try readFileContents(at: filePath)
print("파일 내용: \(content)")
} catch let error as NSError {
switch error.code {
case NSFileReadNoSuchFileError:
print("에러: 파일이 존재하지 않습니다.")
case NSFileReadNoPermissionError:
print("에러: 파일을 읽을 권한이 없습니다.")
default:
print("알 수 없는 에러가 발생했습니다: \(error.localizedDescription)")
}
}
}
파일 읽기 호출
readFileContents를 호출해 파일 내용을 읽어옵니다.
에러가 발생할 가능성이 있으므로 do-catch 구문을 사용합니다.
에러 처리
호출 중 발생할 수 있는 에러를 처리합니다. 에러가 발생하면 catch 블록이 실행되어 적절한 메시지를 출력하게 됩니다.
코드 흐름 상세 설명
1. let filePath = "/path/to/your/file.txt"
- 파일 경로를 지정합니다. (실제로 이 경로에 파일이 있어야 제대로 동작합니다.)
2. do { let content = try readFileContents(at: filePath) }
- 에러가 발생할 가능성이 있는 코드를 실행하는 블록입니다. readFileContents를 호출하면서 try를 사용합니다.
3. catch let error as NSError
- do 블록에서 에러가 발생하면 실행됩니다. 에러를 NSError로 캐스팅해 상세한 에러 정보를 처리할 수 있습니다.
4. switch error.code
- 발생한 에러의 코드를 확인해 여러 유형에 따라 다른 동작을 수행합니다.
- 예를 들어:
- NSFileReadNoSuchFileError: 파일이 없는 경우.
- NSFileReadNoPermissionError: 읽기 권한이 없는 경우.
구체적인 동작 시나리오
1. 정상적인 파일 읽기:
- /path/to/your/file.txt에 파일이 존재하고, 읽기 권한도 있다면:
- readFileContents가 파일 내용을 반환합니다.
- do 블록의 print("파일 내용: \(content)")가 실행됩니다.
2. 파일이 없을 경우:
- 지정된 경로에 파일이 없으면, NSFileReadNoSuchFileError 에러가 발생합니다.
- catch 블록에서 case NSFileReadNoSuchFileError에 매칭되어 "에러: 파일이 존재하지 않습니다." 메시지를 출력합니다.
3. 읽기 권한이 없을 경우:
- 파일은 있지만 읽기 권한이 없으면, NSFileReadNoPermissionError 에러가 발생합니다.
- catch 블록에서 case NSFileReadNoPermissionError에 매칭되어 "에러: 파일을 읽을 권한이 없습니다." 메시지를 출력합니다.
4. 기타 에러 발생 시:
- 위 두 경우 외의 에러가 발생하면 default 케이스가 실행됩니다.
- "알 수 없는 에러가 발생했습니다: \(error.localizedDescription)" 메시지를 출력합니다.
네트워크 요청 실패
네트워크 요청은 클라이언트(앱)가 서버와 통신하여 데이터를 주고받는 과정입니다. 이 과정은 외부 환경에 의존하기 때문에 다양한 이유로 실패할 가능성이 높습니다.
인터넷 연결 문제, 잘못된 URL, 서버 문제, 요청 시간 초과, 인증 실패, 요청 제한 초과와 같은 여러 가지 상황에서 네트워크 요청 실패가 발생합니다.
다음은 주어진 URL로 네트워크 요청을 보내고, 요청 실패, 서버 응답 오류, 데이터 수신 등의 상황을 처리하여 결과를 출력하는 기능을 구현 한 코드입니다.
import Foundation
// 함수 정의: 주어진 URL로 네트워크 요청을 보냄
func fetchData(from url: String) {
// URL이 유효한지 확인
guard let url = URL(string: url) else {
print("잘못된 URL입니다.") // URL 문자열이 잘못되었을 경우 처리
return
}
// URLSession으로 네트워크 요청 생성
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// 네트워크 요청 실패 시 처리
if let error = error {
print("네트워크 요청 실패: \(error.localizedDescription)") // 에러 메시지 출력
return
}
// 서버 응답 상태 코드 확인
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("서버 응답 오류") // HTTP 상태 코드가 성공 범위(200~299) 외일 경우 처리
return
}
// 데이터 처리
if let data = data {
print("데이터를 성공적으로 가져왔습니다: \(data)") // 가져온 데이터를 출력
}
}
// 요청 실행 (비동기적으로 시작)
task.resume()
}
1. 함수 정의
func fetchData(from url: String) {
네트워크 요청을 보낼 때 URL을 문자열 형태로 입력받습니다.
예를 들어, 호출 시 fetchData(from: "https://example.com")처럼 사용할 수 있습니다.
2. URL 유효성 확인
guard let url = URL(string: url) else {
print("잘못된 URL입니다.")
return
}
- URL(string:): 입력받은 문자열이 올바른 URL 형식인지 확인하고, 유효하다면 URL 객체로 변환합니다.
- guard let: URL이 유효하지 않을 경우 else 블록이 실행되어 "잘못된 URL입니다."라는 메시지를 출력하고 함수를 종료합니다.
- 예를 들어, 입력받은 URL이 "not_a_url"처럼 유효하지 않다면 이 블록이 실행됩니다
3. 네트워크 요청 생성
let task = URLSession.shared.dataTask(with: url) { data, response, error in
- URLSession.shared:
- 네트워크 요청을 처리하기 위한 공유 세션 객체입니다. 기본 설정으로 동작하며, 추가적인 설정 없이도 사용할 수 있습니다.
- dataTask(with: url):
- 지정된 URL로 GET 요청을 보내는 비동기 작업을 생성합니다.
- 클로저({ data, response, error in }):
- 네트워크 요청이 완료되면 호출됩니다. 3가지 매개변수를 제공합니다:
- data: 서버에서 반환된 데이터. 성공 시 데이터를 포함합니다.
- response: 서버의 응답 정보(HTTP 상태 코드 등).
- error: 요청 중 발생한 에러가 있으면 이를 포함합니다.
- 네트워크 요청이 완료되면 호출됩니다. 3가지 매개변수를 제공합니다:
4. 네트워크 요청 실패 처리
if let error = error {
print("네트워크 요청 실패: \(error.localizedDescription)")
return
}
- 요청 중 에러가 발생하면 이 블록이 실행됩니다.
- error:
- 네트워크 연결 실패, 타임아웃, DNS 오류 등 다양한 네트워크 문제를 포함할 수 있습니다.
- localizedDescription:
- 에러의 구체적인 설명을 사용자 친화적인 메시지로 제공합니다.
5. HTTP 응답 상태 코드 확인
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("서버 응답 오류")
return
}
- response as? HTTPURLResponse:
- 서버의 응답 객체를 HTTPURLResponse로 캐스팅합니다. HTTP 응답이 아닌 경우(nil) else 블록이 실행됩니다.
- statusCode:
- 서버의 HTTP 상태 코드를 확인합니다.
- 200~299: 요청 성공(OK, Created 등).
- 400~499: 클라이언트 오류(잘못된 요청, 권한 없음 등).
- 500~599: 서버 오류(내부 서버 에러 등).
- 상태 코드가 200~299 범위 밖이면 "서버 응답 오류"를 출력하고 종료합니다.
6. 데이터 확인 및 처리
if let data = data {
print("데이터를 성공적으로 가져왔습니다: \(data)")
}
- data:
- 서버에서 반환된 데이터입니다. 성공적으로 요청이 완료되면 데이터를 포함하며, 실패 시 nil입니다.
- 데이터 처리:
- 여기서는 단순히 데이터를 출력하지만, 실제 애플리케이션에서는 JSON 파싱 또는 다른 데이터 처리를 수행합니다.
- 예: JSON 데이터를 디코딩하여 화면에 표시.
7. 요청 실행
task.resume()
resume():
- URLSessionDataTask는 기본적으로 비활성 상태로 생성됩니다.
- resume()을 호출해야 네트워크 요청이 실제로 시작됩니다.
코드 실행 흐름 요약
- URL이 유효한지 확인하고, 유효하지 않으면 오류 메시지를 출력합니다.
- URL이 유효하면 URLSession을 사용해 네트워크 요청을 생성합니다.
- 요청이 완료되면 다음 상황에 따라 처리합니다:
- 에러가 발생하면 에러 메시지를 출력합니다.
- 서버 응답이 200~299 상태 코드가 아니면 "서버 응답 오류"를 출력합니다.
- 데이터를 성공적으로 가져오면 데이터를 출력합니다.
- 요청 작업을 실행하기 위해 resume()을 호출합니다.
코드가 처리하는 주요 에러 상황
- URL 유효성 검증 실패:
- 잘못된 URL로 인해 요청 자체가 불가능한 경우.
- 네트워크 문제:
- 인터넷 연결이 끊기거나 서버 접근이 불가능한 경우.
- HTTP 상태 코드 오류:
- 서버에서 오류 상태 코드를 반환하는 경우 (예: 404. 500)
- 데이터 없음:
- 요청은 성공했지만 데이터가 반환되지 않은 경우.
사용자 입력 오류
사용자 입력 오류는 사용자가 앱의 입력 필드(예: 텍스트 입력, 폼 제출 등)에 올바르지 않은 값을 제공했을 때 발생합니다. 이러한 오류는 보통 필수 입력값을 누락하거나, 잘못된 형식을 입력하거나, 값이 허용된 범위를 초과하거나, 유효하지 않은 데이터를 입력했을 때 발생합니다.
import Foundation
// 사용자 입력 오류를 정의 (빈 입력값, 잘못된 이메일 형식)
enum ValidationError: Error {
case invalidEmail, emptyField
}
// 이메일 검증 함수
func validateEmail(_ email: String) throws {
// 입력값이 비어 있는지 확인
guard !email.isEmpty else { throw ValidationError.emptyField }
// 이메일 형식 검증 (정규식 사용)
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
guard NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: email) else {
throw ValidationError.invalidEmail
}
}
// 사용자 입력 처리 함수
func handleUserInput(email: String) {
do {
try validateEmail(email) // 이메일 검증 시도
print("유효한 이메일: \(email)") // 검증 성공 시 메시지 출력
} catch {
print("에러: \(error)") // 에러 발생 시 메시지 출력
}
}
// 함수 호출
handleUserInput(email: "example@test.com") // 정상 입력
handleUserInput(email: "") // 빈 입력값
handleUserInput(email: "invalid-email") // 잘못된 이메일 형식
1. enum ValidationError
enum ValidationError: Error {
case invalidEmail, emptyField
}
- 역할:
- 사용자 입력 중 발생할 수 있는 오류를 정의합니다.
- Error 프로토콜 채택:
- Swift에서 에러를 정의하려면 Error 프로토콜을 준수해야 합니다.
- 이 코드는 두 가지 유형의 에러를 정의합니다:
- invalidEmail: 이메일 형식이 잘못된 경우.
- emptyField: 이메일 입력값이 비어 있는 경우.
2. validateEmail 함수
- 역할:
- 이메일 입력값이 올바른 형식인지 검증합니다.
- 문제가 있으면 에러를 던집니다(throws).
세부 동작
1. 비어 있는 입력값 확인:
guard !email.isEmpty else { throw ValidationError.emptyField }
- 입력값이 비어 있는지 확인합니다.
- email.isEmpty가 true이면, ValidationError.emptyField 에러를 던지고 함수 실행을 종료합니다.
2. 정규식 패턴을 통한 형식 검증:
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
guard NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: email) else {
throw ValidationError.invalidEmail
}
- 정규식:
- 이메일 형식을 나타내는 패턴입니다. 예를 들어:
- example@test.com: 올바른 형식.
- example.com: 잘못된 형식.
- 이메일 형식을 나타내는 패턴입니다. 예를 들어:
- NSPredicate:
- 문자열이 정규식 패턴과 일치하는지 평가합니다.
- 일치하지 않으면 ValidationError.invalidEmail 에러를 던집니다.
3. handleUserInput 함수
func handleUserInput(email: String) {
do {
try validateEmail(email) // 이메일 검증 시도
print("유효한 이메일: \(email)") // 검증 성공 시 메시지 출력
} catch {
print("에러: \(error)") // 에러 발생 시 메시지 출력
}
}
역할:
- 사용자로부터 받은 이메일을 검증하고, 결과를 출력합니다.
세부 동작
1. try와 do-catch 사용:
- validateEmail(email)은 throws를 사용하는 함수이므로, 호출할 때 반드시 try를 사용해야 합니다.
- do-catch 구문을 통해 발생한 에러를 처리합니다.
2. 성공 시:
print("유효한 이메일: \(email)")
- validateEmail 함수에서 에러가 발생하지 않으면 "유효한 이메일" 메시지를 출력합니다.
3. 에러 발생 시
catch {
print("에러: \(error)")
}
- 에러가 발생하면 해당 에러를 출력합니다.
- error는 ValidationError 타입이므로, emptyField 또는 invalidEmail 중 하나가 출력됩니다.
4. 함수 호출
handleUserInput(email: "example@test.com") // 정상 입력
handleUserInput(email: "") // 빈 입력값
handleUserInput(email: "invalid-email") // 잘못된 이메일 형식
호출 결과
1. 정상 입력:
- 입력값이 비어 있지 않고, 정규식에 맞는 형식인 경우:
유효한 이메일: example@test.com
2. 빈 입력갑:
- 입력값이 비어 있는 경우 ValidationError.emptyField 에러 발생:
에러: emptyField
3. 잘못된 이메일 형식
- 입력값이 정규식에 맞지 않는 경우 ValidationError.invalidEmail 에러 발생:
에러: invalidEmail
코드 실행 흐름 요약
- 입력값 처리:
- handleUserInput 함수가 이메일 입력값을 받아 validateEmail 함수로 전달합니다.
- 검증 로직:
- validateEmail 함수에서:
- 입력값이 비어 있는지 확인.
- 정규식 패턴으로 형식을 확인.
- validateEmail 함수에서:
- 결과 출력:
- 검증 성공 시: "유효한 이메일" 메시지 출력.
- 검증 실패 시: 적절한 에러 메시지 출력.
마무리
에러 처리는 소프트웨어 개발에서 필수적인 개념이며, 프로그램의 안정성과 사용자 경험을 향상하는 핵심 요소입니다. 위에서 다룬 파일 읽기 실패, 네트워크 요청 실패, 사용자 입력 오류는 실제 앱 개발에서 자주 직면하는 문제들입니다.
그 외에도 여러 가지 에러들이 발생하지만 세 가지 에러만 다뤄봤습니다.
잘못된 내용 혹은 오타가 있거나 더 좋은 내용 피드백은 언제나 환영입니다 :)
'Apple > Swift' 카테고리의 다른 글
| [iOS/Swift] 오버로딩(Overloading) 그리고 오버라이딩(Overriding) (0) | 2024.09.03 |
|---|---|
| [iOS/Swift] 오버라이딩(Overriding) 이란? (2/2) (0) | 2024.08.28 |
| [iOS/Swift] 오버라이딩(Overriding) 이란? (1/2) (0) | 2024.08.26 |
| [iOS/Swift] 상속(Inheritance) 이란? (2) | 2024.08.25 |
| [iOS/Swift] self & super 란? (0) | 2024.08.16 |