지난글에 이어 이번에도 URLSession에 대해 이야기 해보려고 합니다
(지난글 : 2024.06.12 - [Swfit] - [iOS] About URLSession - 1)
이번 글에선 escaping 클로저와 Result Type을 활용하여 지난글의 코드를 개선해보려고 합니다!
가보시죠~
우선 escaping 클로저는 함수가 반환된 후에도 클로저가 호출될 수 있도록 보장하는 키워드입니다.
클로저는 일반적으로 함수내에서 실행되지만..! 비동기 작업에서는 완료된 후에 클로저가 실행될 수 있습니다. 이때 클로저가 함수의 생명 주기를 벗어나기 때문에 escaping을 활용해야합니다
non - escaping
func performTask(task: () -> Void) {
task() // 함수 내에서 클로저를 바로 실행
}
performTask {
print("Task performed!")
}
위의 코드를 보면 클로저가 함수의 스코프를 벗어나지 않기 때문에 escaping 클로저가 필요없고, 이 경우가 일반적인 경우입니다.
escaping
func performAsyncTask(completion: @escaping () -> Void) {
print("1번")
DispatchQueue.global().async {
print("2번")
completion()
}
print("3번")
}
performAsyncTask {
print("Async task completed!")
}
반면에, 위의 코드는 DispatchQueue.global().async를 통해 비동기적으로 실행되는데요,
프린트문 순서를 확인해보면 1번 > 3번 > 2번 임을 알 수 있습니다.
함수 performAsyncTask가 반환된 후에도 클로저가 실행되고 있으므로 @escaping을 활용하여,
함수가 종료된 이후에도 클로저가 실행될 수 있게 힙 메모리에 저장시켜 주고 나중에 호출되더라도 실행에 문제없도록 만들어줍니다.
또한 @escaping과 더불어 많이 쓰이는 친구가 있는데요 바로 Result Type입니다.
Result Type은..!
Generic Enum으로 선언되어있는데요,
success와 failure가 있고 모두 associated value을 가지고 있습니다.
그리고 모든 타입을 받을 수 있는 Success와 Error프로토콜 타입만 받을 수 있는 Failure가 있습니다.
엄청 어려운 내용은 아니고, 몇 번 사용하다보면 금방 익숙해지더라구요
그럼 이들을 바탕으로 이제 다시 지난 주에 사용한 코드를 수정해보면..!
func callRequest(completionHandler: @escaping (Result<Lotto, NetworkError>) -> Void ) {
let url = URL(string: "https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=1100")!
var component = URLComponents()
component.scheme = "https"
component.host = "www.dhlottery.co.kr"
component.path = "/common.do"
component.queryItems = [
URLQueryItem(name: "method", value: "getLottoNumber"),
URLQueryItem(name: "drwNo", value: "1100")
]
let request = URLRequest(url: component.url!, timeoutInterval: 5)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
guard error == nil else {
print("failed request")
completionHandler(.failure(.failedRequest))
return
}
guard let data = data else {
print("no data retruned")
completionHandler(.failure(.invalidData))
return
}
guard let response = response as? HTTPURLResponse else {
print("unable response")
completionHandler(.failure(.invalidResponsec))
return
}
// responseDecodable
guard response.statusCode == 200 else {
print("failed resopnse")
completionHandler(.failure(.failedRequest))
return
}
do {
let result = try JSONDecoder().decode(Lotto.self, from: data)
print("Success")
print(result)
completionHandler(.success(result))
} catch {
print("Error")
print(error)
completionHandler(.failure(.noData))
}
}
}.resume()
}
@escaping을 활용해 Result<Lotto, NetworkError>를 반환해주도록 수정할 수 있습니다.
이전 코드의 일부를 가져와 비교해보면
do {
let result = try JSONDecoder().decode(Lotto.self, from: data)
completionHandler(result, nil)
} catch {
print("Decoding error: \(error)")
completionHandler(nil, .noData)
}
반환해주는 코드를 작성할 때 값타입과 에러타입을 계속해서 반환해주는 것과는 다르게
do {
let result = try JSONDecoder().decode(Lotto.self, from: data)
completionHandler(.success(result))
} catch {
completionHandler(.failure(.noData))
}
다음과 같이 조금 더 편하고 간결하게 코드를 작성할 수 있었습니다.
새롭게 알게된 것들을 바탕으로 이전 코드를 고치는 과정을 요즘 자주 겪게 되는데
코드들이 좀 더 보기 좋게 바뀌는 것 같아 아주 뿌듯한 요즘입니다.
.
.
.
그럼 이번 글은 여기까지
.
.
.
.
'Swift' 카테고리의 다른 글
[iOS] About 싱글턴 패턴 (0) | 2024.06.29 |
---|---|
[iOS] About Router pattern (0) | 2024.06.27 |
[iOS] About Alamofire (0) | 2024.06.23 |
[iOS] About URLSession - 1 (0) | 2024.06.12 |
[iOS] About App SandBox (0) | 2024.05.20 |