주요 내용: 탈출클로저의 개념, 탈출클로저의 쓰임
아래와 같은 함수를 보면 completionHandler
뒤에 붙어 있는 @escaping
이 있습니다.
func dataTask(with url: URL,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
이번 글에서는 @escaping
이 의미하는 탈출클로저가 무엇인지, 어디에 쓰이면 좋을지 살펴보겠습니다.
탈출클로저는@escaping
이 붙어있는 클로저로, 클로저가 함수로부터 '탈출'한다는 것은 해당 함수의 인자로 클로저가 전달되지만, 함수가 반환된 후 실행 되는 것을 의미합니다.
Swift3 이후부터는 기본적으로 함수의 인자로 들어오는 클로저는 함수 안에서만 사용할 수 있습니다. 즉 기본적으로 클로저를 함수 외부의 저장소에 저장하거나, GCD를 이용하여 다른 스레드에서 해당 클로저를 실행시킬 수 없습니다.
그러나 클로저 타입 앞에 @escaping
를 붙이면 탈출클로저로 바꿀 수 있고, 탈출클로저는 해당 함수가 반환된 후 실행되므로, 일반적인 클로저와 달리 함수 외부에 저장하거나 다른 스레드에서 실행시킬 수 있습니다.
그러나 탈출클로저를 이용해 함수를 만들면 함수의 실행이 끝난 후에도 메모리에 해당 부분이 남아있어 외부에서 활용하기 편합니다. ✌️✌️✌️✌️
만약 탈출클로저를 사용할 수 없다면 함수 내 클로저 인자를 함수 외부로 가져와서 가공할 수 없을 것입니다. 따라서 해당 값을 가공하려고 할 때마다 새로운 함수를 생성하여 클로저 인자를 만들고 새로 가공해야 하니 코드가 길고 복잡해질 것입니다.
+) 탈출클로저를 이용하면 깔끔한 아키텍쳐 설계에도 도움이 됩니다.
탈출클로저는 해당함수가 반환된 후 실행되는 클로저이므로, 함수 간 실행순서를 고려할 때 탈출클로저를 사용하여 코드를 짤 수 있습니다. 비동기로 여러 개의 작업이 처리될 때 함수의 동작 순서를 지정할 때 사용할 수 있습니다.
함수 내 클로저 인자를 저장하기 위해서는 함수 밖에서 선언한 변수에 저장하면 됩니다. 아래 예시는 completionHandlers
라는 변수에 someFunctionWithEscapingClosure
라는 함수의 클로저인자인 completionHandler
를 append하여 저장했습니다.
var completionHandlers = [() -> Void]()
func someFunctionWithEscapingClosure(completionHandler: @escaping() -> Void) {
completionHandlers.append(completionHandler)
}
GCD를 활용해 dispatch queue에서 비동기식으로 클로저를 실행할 때 큐는 클로저를 메모리에 보관하고 나중에 사용할 수 있습니다. 이 경우 클로저가 언제 실행되는지 알 수 없습니다. 아래 예시는 json이 담긴 url을 처리하여 DispatchQueue.global(qos: .background).async
에서 처리하도록 하는 예시입니다.
func getJson(path: String, params: [String: Any], completed: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
let url = URL(string: path)
let json = try String(contentsOf: url!, encoding: .utf8)
DispatchQueue.main.async {
completed(Result.success(json))
}
} catch {
DispatchQueue.main.async {
completed(Result.failure(error))
}
}
}
}
탈출 클로저를 이해하는데 도움이 되셨기를 바라면서..이만 총총.