탈출 클로저에 대하여 설명하시오.

이하연·2021년 9월 16일
0

[Swift] 스위프트

목록 보기
14/27

Escping Closure 탈출 클로저

정의

함수의 인자로 전달된 클로저가 함수가 반환된 후 실행 되는 클로저

전달인자로 받은 클로저가 함수 종료 후에 호출될 경우 "클로저가 함수를 탈출한다"로 표현합니다.

사용법

클로저를 매개변수로 갖는 함수를 선언할 때 매개변수 이름 콜론 뒤에 @escaping 키워드를 사용하여 명시

언제 탈출할까?

아래와 같은 경우에 @escaping 을 붙여줘야 하고 그렇지 않을 경우에는 compile 에러가 납니다.

  • 전달 받은 클로저가 클로저 함수 외부로 다시 반환되는 경우
  • 외부 글로벌 변수에 저장되는 경우

이는 기존에 우리가 알고 있던 변수의 scope 개념을 무시하는 것으로 함수에서 선언된 로컬 변수가 로컬 변수의 영역을 뛰어넘어 함수 밖에서도 유효하기 때문입니다.

예시

매개변수로 불러온 클로저를 해당 함수에서 사용하는 것은 전혀 문제가 되지 않습니다.
하지만 이 클로저를 저장하고 다른 곳에서 호출하려고 한다면 컴파일 에러가 발생합니다.
이를 다른 곳에서 사용이 가능하도록 만들기 위해서 @escaping 키워드를 사용해 탈출 클로저로 만듭니다.
즉, 해당 클로저를 어떤 구문 밖으로 탈출시켜서 사용한다는 의미입니다.

아래와 같은 경우 completion을 함수 구문 밖인 completionHandlers 배열에 넣는 것을 볼 수 있습니다.
이를 통해 함수 구문 밖으로 탈출시켜 나중에 언제든 배열에서 꺼내서 사용하겠다를 의미합니다.

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

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

왜 탈출 시킬까?

보통 비동기 작업 을 하기 위해서 클로저를 탈출 시킵니다.

보통 클로저를 가장 자주 사용하는 경우가 Completion Handler 입니다.
예를 들어, 네트워크 요청 작업이 있고 비동기적으로 이를 처리하고 이 처리가 끝난 후 동작하는 것을 Completion Handler에 명령하는 것입니다.

예로 로그인하는 경우
completion을 언제 호출하는지가 관건인데, statusCode==200이란 조건을 만족했을 경우, completion(.success("object"))로 선언하는 것은 통신이 성공했으니 200에 맞는 데이터를 넣어주고 탈출 클로저를 실행해라는 것입니다. 만약 실패했을 경우엔 실패했다는 것을 알 수 있는 데이터를 넣고 탈출 클로저를 실행시켜줍니다.

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)
            }
    } 
}

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

주의

만약, 탈출 클로저임을 명시했을 경우 클로저 내부에서 프로퍼티, 메서드, 서브스크립트 등에 접근하기 위해서는 반드시 self 키워드를 넣어주어야 합니다.

추가 예시

returnOneClosure함수가 클로저를 타입 별칭으로 생성한 VoidVoidClosure를 매개변수로 전달받으면서 return하기 때문에 이 부분에서 탈출이 일어나게 됩니다. 그래서 매개변수 앞에 @escaping을 넣어주었습니다.

typealias VoidVoidClosure = () -> Void

let firstClosure: VoidVoidClosure = {
	print("A Closure")
}

let secondClosure: VoidVoidClosure = {
	print("B Closure")
}

func returnOneClosure(first: @escaping VoidVoidClosure, second: @escaping VoidVoidClosure, shouldReturnFirstClosure: Bool) -> VoidVoidClosure {
	// 이 부분에서 전달받은 first나 second 클로저를 return하기 때문에 탈출 클로저다.
	return shouldReturnFirstClosure ? first : second
}

let returnedClosure: VoidVoidClosure = returnOneClosure(first: firstClosure, second: secondClosure, shouldReturnFirstClosure: true)

returnedClosure() // A Closure 출력

0개의 댓글