[SWIFT] Escaping Closure(탈출 클로저)

2020. 10. 2. 23:15SWIFT

반응형

클로저의 기본에 대해 포스팅을 한 적이 있었는데요.

 

이번에는 클로저 활용에 대해 포스팅을 해볼게요.

 

이전 포스팅을 안보신 분은 보고 오시면 좋을 것 같아요.

 

[SWIFT] Closure(클로저) (1/2)

오늘은 SWIFT 언어의 큰 특징 중 하나인 클로저에 대해 알아보려고 합니다. 우선 클로저를 이해하기 전에 알아야 할 것이 있습니다. SWIFT 언어의 특징이 혹시 기억나시나요⁉️ 바로 함수형 프로��

dongminyoon.tistory.com


@escaping이라는 문구가 붙은 클로저를 혹시 보신적이 있을까요⁉️

 

이게 바로 오늘 포스팅에서 알아볼 탈출 클로저라는 것입니다.

 

더 자세히 예로 볼게요.

func withEscaping(completion: @escaping () -> Void) {
    completion()
}

이런 식으로 @escaping이란 선언이 해당 클로저가 탈출을 허용하다고 명시해주는 것이라고 알면 될 것 같아요.

 

그럼 탈출이란 무엇일까요❓

 

저기 위에 withEscaping 함수에 전달된 클로저로 예를 들어 설명해볼게요.

 

위의 예제처럼 매개변수로 불러온 클로저를 해당 함수에서 사용하는건 문제가 되지 않아요.

그러나 만약, 저 클로저를 저장하고 다른 곳에서 호출하려고 하면 어떻게 될까요?

 

컴파일러 에러로 빨간줄이 호출됩니다.

 

이를 다른 곳에서 사용이 가능하게 만들기 위해서 @escaping을 선언해 탈출 클로저로 사용하는 것입니다.

 

즉, 해당 클로저를 어떤 구문 밖으로 탈출 시켜서 사용하겠다는 뜻입니다.

 

예로 다음과 같은 상황입니다.

// @escaping 선언이 없다면 구문 밖에서 사용이 불가능하기 때문에, 배열에 할당이 불가능합니다.
var completionHandlers: [() -> Void] = []

func withEscaping(completion: @escaping () -> Void) {
    completionHandlers.append(completion)
}

보시면 받아온 completion을 함수 구문 밖인 배열에 넣는 것을 알 수 있죠⁉️

이를 통해, 나중에 언제든 배열에서 꺼내어서 사용하겠다는 것입니다.

 

즉, 함수 구문 밖으로 탈출을 한 것이죠.

 

그렇다면 한 번 더 명시적인 예제를 만들어서 볼게요.

class Myclass {
    var x = 10
    
    func callFunc() {
        withEscaping { self.x = 100 }
        withoutEscaping { x = 200 } 
    }

    var completionHandlers: [() -> Void] = []

    func withEscaping(completion: @escaping () -> Void) {
        completionHandler.append(completion)
    }

    func withoutEscaping(completion: () -> Void) {
        completion()
    }
}

이렇게 생긴 MyClass라는 클래스가 있죠.

그럼 한 번 메소드를 실행시켜보고 결과가 어떻게 나오는지 볼게요.

let mc = MyClass()

mc.callFunc()
print(mc.x) 	// 200

mc.completionHandlers.first?()
print(mc.x)	// 100

callFunc()을 호출하게 되면 먼저 withoutEscaping 메소드가 클로저를 바로 호출하기때문에, x의 값이 200으로 바뀌는 것을 확인 할 수 있어요.

이후 withEscaping에서는 클로저를 바로 호출하는 것이 아니라 completionHandlers 배열에 저장을 하게 되죠⁉️

그리고 호출하는 순간 x의 값이 100으로 바뀌는 것을 확인할 수 있어요.

 

이렇듯이 탈출 클로저을 사용하면 함수 구문 밖에서 사용을 할 수 있어요.

 

 


 

가장 단편적인 예를 보았고,

저는 이 클로저를 가장 자주 사용하고 많이 보았던 경우가 Completion Handler으로 사용하는 경우입니다.

 

예로, 네트워크 요청 작업이 있고 비동기적으로 이를 처리하고 이 처리가 끝난 후 동작하는 것을 Completion Handler에 명령하는 것입니다.

 

아마 iOS 개발을 하면서 네트워크 통신을 해보신 분이면 Completion이라는 변수를 많이 보았죠⁉️

바로 이곳에 여러분들이 원하는 동작을 하는 클로저를 선언해주시면 됩니다.

 

이 클로저들이 @escaping으로 선언되어있는 부분입니다.

 

예로, 제가 로그인에 사용한 코드를 보며 어떻게 사용하는지 볼게요.

private func requestSignup(_ url: String, _ headers: HTTPHeaders?, _ parameters: Parameters?, _ completion: @escaping (NetworkResult<Codable>) -> Void) {
    guard let url = try? url.asURL() else { return }

    AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
        .validate(statusCode: 200...500)
        .responseDecodable(of: APIResponseData<APICantSortableDataResult<SignupResponse>, APIError>.self) { response in
            switch response.result {
                case .success(let signupResponse):
                    guard let statusCode = response.response?.statusCode else { return }
                    if statusCode == 200 {
                        completion(.success(signupResponse.data?.result)) }
                    else { completion(.requestErr(signupResponse.error?.errorMessage)) }
                case .failure:
                    completion(.networkFail)
            }
    } 
}

위에서 주목해야하는 부분은 completion을 언제 호출하는 지입니다.

 

보시면 statusCode == 200이라는 조건을 만족했을 때, completion(.success("object"))로 선언해주시는 것이 보이죠⁉️

 

저부분이 바로 통신이 성공했으니 200에 맞는 데이터를 넣어주고 탈출 클로저를 실행시켜주는 부분입니다.

그렇다면 실패했을 때는 실패했다는 것을 알 수 있는 데이터를 넣고 탈출 클로저를 실행시켜주면 되겠죠❓

 

 

여기는 통신을 하는 코드 부분이었고, completion에 클로저를 넣어주는 부분에서는 로그인 결과에 다른 처리를 해주면되겠죠.

 

✔️ 로그인에 성공했을 때

  • 다음 화면으로 넘어간다.
  • Token 값을 저장한다.

 

✔️ 로그인을 실패했을 때

  • 로그인 실패 팝업 화면을 띄운다.

 

탈출 클로저를 사용해 결과를 받아오고 결과에 맞는 분기처리로 해당 함수 구문 밖에서도 적절한 동작을 할 수 있도록 설계할 수 있답니다.

 

 

오늘은 지난 번에 포스팅 했던 클로저(Closure)에 이어서 활용편인 탈출 클로저(Closure)에 대해 포스팅을 해봤어요🤗

잘못된 부분이나 궁금한 점이 있으면 댓글주세요~~

 

 

 

 

 

 

 

반응형