본문 바로가기
Swift

[iOS] About URLSession - 2

by dsungc 2024. 6. 19.

지난글에 이어 이번에도 URLSession에 대해 이야기 해보려고 합니다

 (지난글 : 2024.06.12 - [Swfit] - [iOS] About URLSession - 1)

 

[iOS] About URLSession - 1

iOS 개발을 하다 보면 이 네트워크 통신은 떼려야 뗄 수 없는 것 같습니다.그저 정해진 화면만 구성하는 것을 넘어흔히 우리가 사용하는 로그인, 실시간으로 정보를 사용자에게 보여주는 등

dsungc08.tistory.com

 

 

이번 글에선 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으로 선언되어있는데요,

 successfailure가 있고 모두 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