에러처리

냐옹·2024년 7월 11일
0

IOS

목록 보기
12/32

본 작성글은 swift 공식 문서를 참고하였습니다.

에러처리

  • Error 프로토콜을 채택하는 모든 타입을 사용하여 에러를 나타낸다.

  • 에러를 던지기 위해서 throw를 사용하고 에러를 던질 수 있는 함수를 나타내기 위해서 throws를 사용한다.

  • 함수에서 에러가 발생하면 함수는 즉시 반환되고 함수를 호출한 코드가 에러를 처리한다.

func send( job : Int, toPrinter printerName : String ) throws -> String {
	if printerName == "Never Has Toner"{
    	throw PrinterError.noToner
    }
  	return "Job sent"
}
  • 에러를 처리하는 방법은 여러가지가 있다.
  1. do-catch
do{
	let printerResponse = try send(job : 1040, toPrinter : "Bi Sheng")
  	// 앞에서 send 함수에 'throws'를 명시해줬기 때문에 try 키워드를 앞에다가 붙여야 한다. 
}catch{
  	print(error)
}
// JobSent
  1. 특정 에러를 처리하는 여러개의 catch블럭
  • 스위치에서 case이후에 하는 것처럼 catch 이후에 패턴을 작성한다.
do{
  	let printerResponse = try send(job : 1440, toPrinter : "Gutenberg")
}catch PrinterError.onFire{
	print("~~~")
}catch let printerError as PrinterError{
	print("Printer error : \(printerError)")
}catch{
	print(error)
}
// "Job sent"
  1. try?
  • 함수에서 에러가 발생하면 특정 에러는 버려지고 결과는 nil이다.
  • 그렇지 않으면 결과는 함수가 반환한 옵셔널 값을 포함한다.
let printerSuccess = try? send(job : 1884, toPrinter : "Mergenthaler")

let printerFailure = try? send(job : 1885, toPrinter: "Never Has Toner")
  • defer : 함수를 반환하기 직전에 함수의 다른 모든 코드 다음에

rest api를 사용하면서 마주칠 수 있는 Errors

1. URLSession 관련 에러
  • URLSessionError : URLSession에서 발생하는 일반적인 에러로 네트워크 요청이 실패했을 때 나타난다.

  • <세부에러>

  • URLError.notConnectedToInternet
    인터넷 연결이 없는 경우

  • URLError.timedOut
    요청이 타임아웃된 경우

  • URLError.cannotFindHost
    호스트를 찾을 수 없는 경우

  • URLError.cannotConnectToHost
    호스트에 연결할 수 없는 경우

  • URLError.networkConnectionLost
    네트워크 연결이 도중에 끊어진 경우

  • URLError.badServerResponse
    서버로부터 잘못된 응답을 받은 경우

  • URLError.unsupportedURL
    지원되지 않는 URL을 사용하는 경우

2. HTTP 상태코드 에러

이건 패스

3. JSON Parsing Error
  • DecodingError.dataCorrupted
    JSON 데이터가 손상된 경우 발생

  • DecodingError.keyNotFound
    JSON에서 필요한 키를 찾을 수 없는 경우

  • DecodingError.typeMismatch
    JSON데이터의 타입이 일치하지 않는 경우

  • DecodingError.valueNotFound
    JSON에서 필요한 값을 찾을 수 없는 경우

예시코드

do{
  	let result = try ~~api 호출하는 함수
}catch let corruptError = DecodingError.dataCorrupted{
	print("Corrupt 에러 : \(corruptError)")
}catch{
	print("정의되지 않은 에러 : \(error)")
}
4. Custom Error
  • 개발자가 정의한 커스텀 에러.
enum NetworkError : Error{
	case invalidURL
  	case noData
  	case decodingFailed
  	case custom(message : String)
}
  • @escaping 키워드는 클로저가 함수의 실행이 끝난 후에도 호출될 수 있음을 나타내는 속성이다.
  • 클로저가 함수 외부에서도 사용되거나 저장될 수 있음을 명시하는데 사용된다.
  • 기본적으로 클로저는 함수 내에서 실행되고 함수가 종료되면 사라진다.
  • 그러나, 비동기 작업이나 콜백 함수에서 클로저가 함수의 실행이 끝난 후에 호출되어야 하는 경우가 있는데, 이 경우에 클러저에 @escaping 속성을 사용한다.
  • * 이렇게 설명을 들었는데도, 솔직하게 @escaping이 이해가 안된다.

  • @escaping이 필요한 이유는 클로저가 함수의 실행이 끝난 후에도 호출될 수 있음을 명시적으로 나타내기 위해서이다.

  • 구체적으로, 클로저가 함수 외부에서 참조되거나 저장되어 함수가 반환된 이후에도 클로저가 사용될 때 @escaping이 필요하다. 이를 통해서 swift는 메모리 관리와 관련된 최적화를 올바르게 수행할 수 있다.

  1. 비동기작업
  • 비동기 작업에서는 함수가 종료된 후에도 클로저가 실행될 수 있다.

  • 예를 들어서, 네트워크 요청의 결과를 처리하는 클로저는 네트워크 응답이 도착할 때까지 대기하기 때문에 함수가 종료된 후에 실행된다.

  • (참고) swift에서
    함수는 Named Closure
    익명함수는 Unnamed Closure ( 보통 클로저라고 하면 이를 말한다 )

  • 비동기작업에서의 예시코드를 한번 보자.
  • 네트워크 요청의 결과를 처리하는 클로저는 네트워크 응답이 도착할 때까지 대기하기 때문에 함수가 종료된 후에 실행된다.
func fetchData(
	from urlString : String,
  	completion : @escaping ( (Result<Data, Error>) -> Void ) throws -> Void
	// 여기에 escaping을 쓰는 이유는 unnamed closure인 익명함수를 다시 재사용할 것이기 때문이다. 일회용이 아니라고 적어놓은 것
){
	guard let url = URL(string : urlString) else{
    	// 만약에 urlString을 URL에 인자로 준게 NIL이 뜬다면
      	completion(.failure(error))
        return
      	// guard let은 return을 꼭 해줘야함. switch의 case 문이라고 생각해야댐
    }
  
  	// guard let을 통과한 상수는 전역 변수로서 사용할 수 있다.
  	let task = URLSession.shared.dataTask(with : url){
    	data, response, error in
          if let error = error{
          	// 만약에 error가 발생한다면
            completion(.failure(error))
            // 에러 처리해주고
            return
            // 함수 종료
          }
      
      	  guard let httpResponse = response as? HTTPURLResponse,
            	(200..299).contains(httpResponse.statusCode) else{
    			// 만약에 response가 nil이거나 statuscode가 200대가 아닐 경우에
          		completion(.failure(NetworkError.custom(message:"Invalid response from server")))
                
                return
    		}
  
  		  guard let data = data else{
          	// 만약에 data가 안들어오면
            completion(.failure(NetworkError.noData))
            return
          }
  
  		  completion(.success(data))
    }
	task.resume()
}


do{
	let result = try fetchData(from : "api 주소"){
	result in 
      switch result{
        case .success(let data) : 
        	print("Data Received: \(data)")
        case .failure(let error) : 
        	print("Error occurred: \(error)")
      }
}
}catch{
	print("error : \(error)")
}
try? 와 try!
  • 함수를 구현할때 throws 키워드를 달아놓으면, 그 함수를 호출할 때 try와 에러에 대한 처리를 강제한다.

그러면 다음과 같은 구조가 된다.

do{
  	const result = try ~~~~
}catch{
	... 
}
  • 여기에서 try, try?, try!를 쓸 수 있는데 한번 보자.
    일단 모두 에러가 발생할 가능성이 있는 코드블록을 처리하는데에 사용된다.

try는 일반적인 경우에 사용된다.
try?는 안전한 에러처리를 위해서 사용된다.
try!는 에러가 발생하지는 않을 것이라고 확신할때 사용하나, 사용하지말자(스스로를 믿지마라)

  • try?
    try?는 에러가 발생하면 nil을 반환한다. 에러가 발생하지 않으면 옵셔널 타입으로 값을 반환한다.
    이 경우에 do-catch문에서 쓸일은 없을 것 같고 다음과 같이 사용 이후에 if let에서 사용한다.
func someFunctionThatThrows() throws -> String{
	// 에러를 던질 가능성이 있는 함수 (throws)
	return "Success"
}

let result = try? someFunctionThatThrows()

if let result = result{
	print("정상 처리 ~~~\(result)")
}else{
	print("에러")
}
  • try!
    쓰지마라

0개의 댓글