이전에 클로저에 대해서 정리하면서 Escpaing 클로저에 대해서도 다루어 보았다.
Escpaing클로저는 특히 비동기 함수들을 처리할 때 유용한데 최근 다룰일이 있었다.
이번에 자세히 알아보고 제대로 사용하도록 하자.
일단 클로저와 Escaping클로저에 대해서 간단하게 정리해보고 넘어가자
어떤 개념에 대해 습득하고 나중에 실제로 적용해보려고 하면 헷갈리는 경우가 많다.
그때마다 제대로 이해한게 아니구나 라는 생각을 하곤한다.
직접 사용해보면서 제대로 이해해 보자
일단 아래의 코드를 보자
func makeCupnoodles() {
print("뜨거운 물을 부었습니다.")
DispatchQueue.global().async {
sleep(180)
print("면이 다 익었습니다.")
}
print("뚜껑을 닫았습니다.")
}
makeCupoodles()
print("맛있게 먹겠습니다!")
makeCupnoodles()는 컵라면을 만드는 메서드이다.
먼저 뜨거운 물을 붓고 3분을 기다린다. 우리는 3분동안 딴짓을 하기위해 비동기로 3분을 기다린다. 그리고 물이 다 익었다면 맛있게 먹으면 된다.
출력 ㄱㄱ
뜨거운 물을 부었습니다.
뚜껑을 닫았습니다.
맛있게 먹겠습니다!
면이 다 익었습니다.
면이 다 익기전에 먹어버린다...
이전에 동기/비동기에 대해서 다룬적이 있었는데 동기는 기다린다, 비동기는 안기다린다가 핵심이라고 했다. 그렇기 때문에 비동기로 작동하는 '면 익히기'를 기다리지 않고 함수는 종료되고 다음 줄의 코드인 "맛있게 먹겠습니다."프린트문을 실행시킨다.
클로저는 이런 비동기 상황에서 유용하다고 했는데 그러면 클로저를 어떻게 이용할 수 있을까?
func makeCupnoodles(completion: () -> Void) {
print("뜨거운 물을 부었습니다.")
DispatchQueue.global().async {
sleep(180)
print("면이 다 익었습니다.")
completion()
}
print("뚜껑을 닫았습니다.")
}
makeCupnoodles {
print("맛있게 먹겠습니다!")
}
completion을 추가하여 면이 다 익으면 completion 클로저를 실행하게 하고 makeCupnoodles를 호출 할 때 completion을 정의해준다.
그런데 이렇게만 하면 제대로 작동되지 않는다.
Escaping 클로저가 non-escaping 매개변수 'completion'을 캡처하고 있다는 에러 문구가 뜬다. 위에서 설명한대로 Escaping 클로저는 함수를 빠져나가서 저장되거나 전달 될 수 있다고 했다. 현재 completion은 함수가 종료된 후에 사용되기 때문에 에러가 뜨는것이다. 그렇기 때문에 @escaping을 작성해주어 non-escaping클로저를 escaping 클로저로 명시해주도록 하자.
func makeCupnoodles(completion: @escaping () -> Void) {
print("뜨거운 물을 부었습니다.")
DispatchQueue.global().async {
sleep(180)
print("면이 다 익었습니다.")
completion()
}
print("뚜껑을 닫았습니다.")
}
makeCupnoodles {
print("맛있게 먹겠습니다!")
}
// 뜨거운 물을 부었습니다.
// 뚜껑을 닫았습니다.
// 면이 다 익었습니다.
// 맛있게 먹겠습니다!
이제 면이 다 익기를 기다리고 먹을 수 있게 되었다.
핵심 차이는 함수 실행중에 호출되는지, 종료 후에도 호출되는지의 차이다.
그냥 단순히 아래의 코드처럼 작성하면 문제없이 잘 실행된다.
func makeCupnoodles(completion: @escaping () -> Void) {
print("뜨거운 물을 부었습니다.")
print("면이 다 익었습니다.")
completion()
print("뚜껑을 닫았습니다.")
}
makeCupnoodles {
print("면이 다 익었습니다.")
}
// 뜨거운 물을 부었습니다.
// 뚜껑을 닫았습니다.
// 면이 다 익었습니다.
// 맛있게 먹겠습니다!
completion이 호출되는 시점에 makeCupnoodles가 종료되기 전이기 때문이다.
하지만 원래의 상황처럼 비동기 작업은 함수가 해당 작업을 기다리지 않고 종료되기 때문에 completion을 호출할 수 없는것이다.
이번에 escaping클로저에 대해 다시한번 알아보았는데 한번 배울 때 안까먹고 바로 이해하면 좋겠지만 인간의 뇌는 반복해서 사용하지 않으면 중요한 정보라고 생각하지 않고 버린다고 한다. 그러니 여러번 반복하고 다시 알아보면서 뇌에게 중요한 정보라는것을 알리는게 좋다.
escaping 클로저는 함수의 범위를 벗어나 저장하거나 전달할 수 있는 특성으로 비동기적인 작업, 콜백 기능 등 Swift API들에서 자주 사용되고 유용하게 활용할 수 있다.
다만 메모리 관리에 주의를 기울여 적절한 순간에 사용하기 위해 명확한 이해가 피요하다.