Escaping Closure로 비동기 함수 처리해보기

고라니·2023년 12월 15일
0

TIL

목록 보기
52/67

이전에 클로저에 대해서 정리하면서 Escpaing 클로저에 대해서도 다루어 보았다.
Escpaing클로저는 특히 비동기 함수들을 처리할 때 유용한데 최근 다룰일이 있었다.
이번에 자세히 알아보고 제대로 사용하도록 하자.

클로저와 Escaping클로저

일단 클로저와 Escaping클로저에 대해서 간단하게 정리해보고 넘어가자

클로저

  • 독립적으로 실행 가능한 코드 블럭, 일반적으로 우리가 부르는 함수는 이름이 있는 클로저
  • 클로저는 변수나 상수를 캡처할 수 있어서 외부의 값을 저장하고 나중에 사용할 수 있음

Escaping클로저

  • 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("맛있게 먹겠습니다!")
}

// 뜨거운 물을 부었습니다.
// 뚜껑을 닫았습니다.
// 면이 다 익었습니다.
// 맛있게 먹겠습니다!

이제 면이 다 익기를 기다리고 먹을 수 있게 되었다.

escaping클로저와 그냥 클로저의 차이

핵심 차이는 함수 실행중에 호출되는지, 종료 후에도 호출되는지의 차이다.

그냥 단순히 아래의 코드처럼 작성하면 문제없이 잘 실행된다.

func makeCupnoodles(completion: @escaping () -> Void) {
    print("뜨거운 물을 부었습니다.")
    print("면이 다 익었습니다.")
    completion()
    print("뚜껑을 닫았습니다.")
}

makeCupnoodles {
	print("면이 다 익었습니다.")
}
// 뜨거운 물을 부었습니다.
// 뚜껑을 닫았습니다.
// 면이 다 익었습니다.
// 맛있게 먹겠습니다!

completion이 호출되는 시점에 makeCupnoodles가 종료되기 전이기 때문이다.

하지만 원래의 상황처럼 비동기 작업은 함수가 해당 작업을 기다리지 않고 종료되기 때문에 completion을 호출할 수 없는것이다.

마치면서

이번에 escaping클로저에 대해 다시한번 알아보았는데 한번 배울 때 안까먹고 바로 이해하면 좋겠지만 인간의 뇌는 반복해서 사용하지 않으면 중요한 정보라고 생각하지 않고 버린다고 한다. 그러니 여러번 반복하고 다시 알아보면서 뇌에게 중요한 정보라는것을 알리는게 좋다.
escaping 클로저는 함수의 범위를 벗어나 저장하거나 전달할 수 있는 특성으로 비동기적인 작업, 콜백 기능 등 Swift API들에서 자주 사용되고 유용하게 활용할 수 있다.
다만 메모리 관리에 주의를 기울여 적절한 순간에 사용하기 위해 명확한 이해가 피요하다.

profile
🍎 무럭무럭

0개의 댓글