Swift Escaping Closure ??
참고자료
- Apple Inc. The Swift Programming Language (Swift 3.1) - Escaping Closure
- escaping closure swift3
- What do mean @escaping and @nonescaping closures in Swift?
- Completion handlers in Swift 3.0
https://hcn1519.github.io/articles/2017-09/swift_escaping_closure
https://medium.com/@jgj455/%EC%98%A4%EB%8A%98%EC%9D%98-swift-%EC%83%81%EC%8B%9D-closure-aa401f76b7ce
Escaping Closure 개념
클로저가 함수로 부터 escape 한다는 것은 해당 함수의 인자로 클로져가 전달되지만, 함수가 반환된 후 (Closure 가) 실행되는 것 혹은 함수가 밖에서 (Closure 가) 실행되는 것을 의미한다.
함수의 인자가 함수의 영역을 탈출하여 함수 밖에서 사용할 수 있는 개념은 scope 개념을 깨는 것이다. 함수에서 선언된 로컬변수가 로컬변수의 영역을 뛰어넘어 함수 밖에서도 유효하기 때문이다. 그렇기에 함수 밖에서 유효하다고 하여 전역변수와 비교할 수 있지만 다른점이 존재한다.
—> 클로저의 Escaping는 A 함수가 마무리된 상태에서만 B함수가 실행되도록 함수를 작성할 수 있기 때문이다. 이를 활용해서 함수 사이에 실행 순서를 정할 수 있다.
이러한 실행순서를 정한다는 장점을 이용해서 Escaping Closure는 다음 두 경우에서 자주 사용한다.
- 비동기로 실행되는 경우 (실행 순서에 제한이 있어야 하는 경우)
- complitionHandler(완료에 따른 처리) 로 사용되는 클로저의 경우
주의 할점 2가지 (밑에서 사례를 보며 찝어보겠다.)
- 클로져가 함수 외부에서 사용하고 싶으면 escaping closure로 선언해야 한다. (만약 함수가 끝나고 실행되는 함수는 escaping closure로 선언해야 한다.) 아니면 에러발생
- Escaping closure를 사용하는 클로저에서는 self를 명시적으로 언급해야한다.
함수의 실행순서를 결정할 수 있다? (비동기로 실행되는 경우)
함수의 실행 순서를 보장 받을 수 있는 것은 굉장히 중요한 기능이다.
우리는 Networking을 배우며 비동기로 실행되는 Task들을 보았다. 여기서 연관되어 있는 Task들은 순서를 지정해 주지 않으면 문제가 생기는 경우가 존재한다.
예를 들면 서버에서 JSON 형식의 데이터를 가져와서 화면에 이를 보여주는 앱이 있다고 해보자.
이때, HTTP 통신을 위해 Alamofire 라이브러리를 사용하였다.
Alamofire.request(urlRsquest) 메소드는 서버로 Request를 전송한다.
이때 GET방식으로 JSON 데이터를 받아오게 되는데 이때 respondeJSON을 호출하여 Response 객체를 response로 받아오게 된다.
request와 response 가 포함된 클로저는 비동기로 실행된다. 응?? 요청 전 응답??
—> 그렇게 되면 request가 정상적으로 전송되고 responseJSON() 이 반환되기전에 handler closure가 호출될 수 있다. ???
—> 이렇게 되면 request를 전송받아 responseJSON()이 모든 작동이 실행되기전 response를 받아서 클로져가 호출되면 데이터를 받아오지 못할 수 있는데 ???
여기에 대한 답이 @escaping 이다.
우리는 Asyc(비동기) 함수들이 있을때 이를 제어할 수 있기 때문이다.
responseJSON 함수를 살펴보면 다음과 같다.
completionHandler에서 클로져가 @escaping로 선언 되어있다.
이것이 무슨말이냐? responseJSON에서 다른 코드 기능들을 모두 수행한 다음 completionHandler를 호출해서 위에서 걱정한 비동기 함수로 인한 문제를 없앨수 있다는 것이다.
Escaping closure로 선언되었기에 responseJSON이 기능에 해당하는 부분을 모두 수행한 다음 return될때 escaping closure를 호출하기에 정상적인 response를 받아올 수 있었던 것이다.
함수가 반환되고 완전히 서버로부터 값을 가져온 상태에서 실행이 된다.
실행되는 부분이 바로 trailing closure로 작성되어 있는 { response in } 이다.
한번 정리하고 가자.
함수에서 인자로 클로져를 받을 수 있다. 받아온 클로져는 인자로 들어온 함수에서 동작해야하고 해당 scope를 벗어날 수 없다. 지역변수가 지역안에서만 유효한 것과 같은 것이다.
여기서 함수 외부에서 함수 내부에 존재하는 클로져를 호출하거나 함수 사이에 실행순서를 제어하는 기능이 등장한다.
함수가 return까지 하고 완전히 완료된 상황에서 실행되야 하는 경우나 비동기적인 함수의 실행 순서를 제어하는 경우가 대표적인 예다. 이때 등장하는 개념이 escaping closure이다.
인자로 받은 closure앞에 @escaping를 선언하게 되면 해당 클로져를 escaping closure 라 부르고, 실행되는 함수가 return된 다음에 escaping closure를 실행하는 것과 같이 함수 외부에서 내부에 존재하는 클로져를 실행할 수 있고, 비동기 함수의 실행순서를 제어할 수 있기 때문이다.
즉, 외부나 내부에서 원하는 시점에 클로져를 호출할 수 있는 것이다.
closure를 함수 외부에 저장하기
다음의 코드를 살펴보면 내부에 존재하는 클로저를 외부로 끄집어 내는 행위까지 할 수 있다.
Escaping Closure가 외부에 존재하는 변수에 할당되는 것을 확인할 수 있다.
주의할점 1번: 이렇게 함수를 호출하는 도중에 해당 함수 외부에 클로저를 저장하기 위해서는 클로져가 escaping closure 이어야 한다!!!!
주의할점 2번: escaping closure를 사용하는 withEscaping를 호출하는 부분에서는 명시적으로 self를 지정해줘야 한다.
Async Inside Async (네트워킹)
Escaping Closure의 경우는 HTTP 통신에서 completionHandler로 많이 사용된다.
다만, 서버에 요청하는 RestfulAPI 기반의 Request 들은 앱의 이곳저곳에서 사용되는 경우가 많기 때문에 따로 클래스를 만들어서 사용하는 것이 유용할 것이다.
여기에서는 이러한 클래스를 구현하는 방식으로 Class안에서 통신메소드들을 static함수 형태로 관리하는 것을 구현해 보고자 한다.
동작순서를 살펴보자.
- ViewController에서 필요한 데이터를 얻기위해 Server.getPerson(completion:)을 호출한다. 이렇게 되면 completion을 해당 함수 외부나 실행 순서를 제한할 수 있다.
- Alamofire를 통해 서버로 Request를 전송하고 responseJSON은 Escaping Closure이므로 {response in} 부분은 결과가 모두 들어온 이후에 실행된다. —> responseJSON은 request로 받은 데이터 통해 정상적인 데이터를 반환하기 위한 모든 Task를 수행한 뒤 Response 객체를 받아온다. 그후 내부에 존재하는 completionHandler 블록이 실행되고 이것이 {response in} 클로져이다.
- responseJSON의 completionHandler 블럭이 실행되고 이 블록은 {response in} 블록이다. 그렇다면 현재 response에서는 response data가 json 형식으로 들어가 있는 것이다. 이 받아온 데이터를 persons에 추가한다.
- 화면 업데이트를 위해 서버로부터 받아온 데이터(persons)를 처음 호출했던 ViewController 쪽으로 보내기 위해, getPerson(completion:)의 completion을 호출합니다. 그런데 이 때, 화면 업데이트는 Main 쓰레드에서 이뤄져야하므로, completion은 Escaping Closure 형태를 취합니다. —> 아하! Main Thread는 현재 부분이 아닌 외부에 존재하는 scope이기에 completion을 사용하려면 escaping closure로 선언되어야 하는구나~
- 호출된 completion으로 getPerson(completion:) 메소드의 completion 블럭이 실행됩니다. 이 때, 통신이 잘 되었는지, 확인하는 Boolean을 isSuccess로 넘기고, 데이터를 persons로 넘겼습니다. 그 이후 화면을 업데이트하면 앱에서 서버의 데이터를 문제 없이 받아오게 됩니다.
- { (isSuccess, persons) in } 에 해당하는 클로져가 true, persons라는 데이터를 인자로 받아서 호출된 함수 외부영역인 MainThread영역에서 호출하여 Task가 실행되는 구조이다.
위의 코드에서는 getPerson을 static 메소드를 이용해 type method로 선언했다.
—> 이제 앱 어디에서든 Server.getPerson()으로 호출이 가능해 졌다.
서버에서 JSON 정보를 가져와 앱 화면에 보여주는 경우를 다시 생각해보겠다.
우선적으로 생각할 것은 데이터를 받아오는 것과 데이터를 이용해 화면을 업데이트 하는 것이 모두 비동기로 이루어 져야 한다는 것이다. 그렇다면 데이터를 받아온 상태에서 화면을 업데이트 하는 것이 보장되어야한다.
그렇지 않으면 화면 업데이트할 데이터가 없어 크래시가 발생하기 때문이다.
—> 이와 같은 경우에는 두개의 Escaping Closure 를 사용한다.
우선 두개의 Escaping Closure는 다음과 같다.
- responseJSON (내부에 Escaping Closure가 존재한다.)
- getPerson에서 인자로 선언된 클로져
'IOS application > Swift' 카테고리의 다른 글
9. Keyboard (0) | 2021.04.24 |
---|---|
8. Tabbar, SafeArea, collectionView 설정 (0) | 2021.04.24 |
6. Instance Method vs. Type Method (0) | 2021.04.22 |
5. AVMetadataItem (0) | 2021.02.18 |
4. Design Patton: MVVM 패턴 (0) | 2021.02.06 |