네트워크 통신 기능을 구현하는 경우 서버에서 온 응답값을 `completionHandler`를 통해 `@escaping`의 형태로 함수 밖으로 값을 빼내는 방식을 사용하게 된다.
결국, 서버에서 온 응답값을 함수 밖에서 사용한다는 뜻인데 문득 이런 궁금증이 들었다.
"함수 밖으로 값을 뺀다는 것은 함수의 반환값으로도 할 수 있지 않을까?"
이번 글에서는 네트워킹 코드에서 반환값을 사용할 경우의 문제점과 함께 `@escaping 클로저`에 대해 알아보도록 하겠다.
참고로 네트워킹 코드는 Alamofire를 기준으로 작성되었다.
반환값으로 네트워킹 코드 작성시 문제점
백문이 불여일견이라고, 원래 `completionHandler`와 `@escaping 클로저`를 통해 작성되어있는 네트워킹 코드를 작성해보겠다.
아래 코드는 원래 방식의 코드이다.
func callKakaoBookAPI(query: String, page: Int, completionHandler: @escaping (Book) -> Void ) {
let url = "https://dapi.kakao.com/v3/search/book?query=\(query)&size=20&page=\(page)"
let header: HTTPHeaders = [
"Authorization": APIKey.kakao
]
AF.request(url, method: .get, headers: header)
.responseDecodable(of: Book.self) { response in
switch response.result {
case .success(let value):
completionHandler(value)
case .failure(let error):
print(error)
}
}
}
코드의 동작은 간단하다. url을 통해 kakaoAPI를 request하고, 그 응답값을 Book이라는 구조체에 담아 completionHandler를 통해 함수 밖으로 escaping 시키고 있다.
해당 코드를 사용할때는 그래서 다음과 같이 클로저의 형태로 사용하게 된다.
func callRequest(query: String) {
NetworkManager.shared.callKakaoBookAPI(query: query, page: page) { value in
self.list = value.documents
}
}
그렇다면 Book타입의 응답값을 다음과 같이 함수의 반환값에 담아 사용할 수도 있지 않을까?
func callKakaoBookTest(query: String, page: Int) -> Book {
let url = "https://dapi.kakao.com/v3/search/book?query=\(query)&size=20&page=\(page)"
let header: HTTPHeaders = [
"Authorization": APIKey.kakao
]
AF.request(url, method: .get, headers: header)
.validate(statusCode: 200..<500)
.responseDecodable(of: Book.self) { response in
switch response.result {
case .success(let value):
print("✅SUCCESS")
return value
case .failure(let error):
print(error)
}
}
}
그럼 사용할때도 다음과 같이 간단하게 사용할 수 있을것 같았다.
func callRequest(query: String) {
let result = NetworkManager.shared.callKakaoBookTest(query: query, page: page)
list = result.documents
}
그런데, 반환값으로 구성된 코드를 작성하자마자 다음과 같은 오류가 발생했다.
현재 이 함수는 Book이라는 타입을 반환하는 함수이다.
그래서 AF구문에서 반환하는 값 이외에도 어쨌든 Book이라는 타입을 반환해야 하는 상황이라는 것이다.
일반적으로 네트워킹 코드는 시간이 오래걸릴 수 있기 때문에 메인쓰레드가 아닌 글로벌 쓰레드에서 동작하게 된다.
그래서 AF구문은 `callKakaoBookTest()`메서드보다 늦게 끝나게 된다.
즉, 네트워크에서 응답값이 오기 전에 메서드의 생명주기가 먼저 끝나게 되므로, 응답값과는 상관 없는 `what`라는 이상한 값이 return될 수 밖에 없는 것이다.
지금의 상황을 그림으로 표현하자면 다음과 같다.
네트워킹이 종료되기 전에 함수가 먼저 종료됨으로, 네트워킹의 응답과 상관없는 이상한 값이 return되는 문제가 발생한다.
그렇다면 @escaping은 어떤 녀석이길래 네트워킹의 응답값을 성공적으로 반환할 수 있던 것일까?
@escaping 클로저
개발자문서에 따르면 @escaping 클로저에 대한 설명은 다음과 같다.
클로저가 함수에 인수로 전달되지만 함수가 반환된 후에 호출될 때 클로저는 함수를 탈출한다고 합니다. 클로저를 매개변수 중 하나로 사용하는 함수를 선언할 때 매개변수의 유형 앞에 @escaping을 작성하여 클로저가 탈출할 수 있음을 나타낼 수 있습니다.
@escaping 클로저는 함수가 반환된 이후에 호출되는 동작을 한다고 볼 수 있다. 그리고 반환값은 함수 외부에서 정의된 변수에 저장된다.
즉, 함수는 작업을 마친 후 어떤 값을 반환하지만, 작업이 완료될때 까지 클로저가 호출되지 않고, 함수 외부에서 매개변수를 통해 저장된 반환값을 사용할 수 있는 것이다.
이러한 특징 덕분에 네트워킹 코드에서 함수의 생명주기와 관계없이 네트워크의 응답값을 사용할 수 있었던 것이며, 꼭 네트워킹 코드가 아니더라도 비동기 작업을 하는 함수들은 completionHandler로 @escaping 클로저 인수를 사용한다.
※ 참고자료
- https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/
'iOS > Swift' 카테고리의 다른 글
[iOS/Swift] WMO와 Method Dispatch - class에 final을 붙이는 진짜 이유 (0) | 2025.01.25 |
---|---|
[iOS/Swift] GCD 골든벨 - 다양한 상황에서의 동기/비동기처리 출력결과 예측하기 (0) | 2025.01.22 |
[iOS/Swift] 네트워크통신 - 데이터를 Class가 아닌 Struct형태로 받는 이유 (0) | 2025.01.13 |
[iOS/Swift] Any와 AnyObject - 모든 타입의 값을 포함할 수 있는 타입 (0) | 2025.01.10 |