iOS/Common

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

TDCIAN 2023. 4. 10. 07:22

교보문고 링크 걸어놨습니다!

 

갑자기 탈출이라고?

함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출(Escape)한다고 표현합니다.

클로저를 매개변수로 갖는 함수를 선언할 때 매개변수 이름의 콜론(:) 뒤에 @escaping 키워드를 사용하여 클로저가 탈출하는 것을 허용한다고 명시해줄 수 있습니다.

 

비동기 작업으로 함수가 종료되고 난 후 호출할 필요가 있는 클로저를 사용해야 할 때 탈출 클로저(Escaping Closure)가 필요합니다.

만약 @escaping 키워드를 따로 명시하지 않는다면 매개변수로 사용되는 클로저는 기본적으로 비탈출 클로저입니다. 함수로 전달된 클로저가 함수의 동작이 끝난 후 사용할 필요가 없을 때 비탈출 클로저를 사용합니다.

 

클로저가 함수를 탈출할 수 있는 경우 중 하나는 함수 외부에 정의된 변수나 상수에 저장되어 함수가 종료된 후에 사용할 경우입니다.

예를 들어 비동기로 작업을 하기 위해서 컴플리션 핸들러를 전달인자를 이용해 클로저 형태로 받는 함수들이 많습니다.

 

함수가 작업을 종료하고 난 이후(즉, 함수의 return 후)에 컴플리션 핸들러, 즉 클로저를 호출하기 때문에 클로저는 함수를 탈출해 있어야만 합니다. 함수의 전달인자로 전달받은 클로저를 다시 반환(return)할 때도 마찬가지입니다.

 

함수를 탈출하는 클로저의 예

typealias VoidVoidClosure = () -> Void

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

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

// first와 second 매개변수 클로저는 함수의 반환 값으로 사용될 수 있으므로 탈출 클로저입니다.
func returnOneClosure(
	first: @escaping VoidVoidClosure, 
    second: @escaping VoidVoidClosure, 
    shouldReturnFirstClosure: Bool
) -> VoidVoidClosure {
	// 전달인자로 전달받은 클로저를 함수 외부로 다시 반환하기 때문에 함수를 탈출하는 클로저입니다.
    return shouldReturnFirstClosure ? first : second
}

// 함수에서 반환한 클로저가 함수 외부의 상수에 저장되었습니다.
let returnedClosure: VoidVoidClosure = returnOneClosure(
	first: firstClosure,
    second: secondClosure,
    shouldReturnFirstClosure: true
)

returnedClosure() // "Closure A" 출력

var closures: [VoidVoidClosure] = []

// closure 매개변수 클로저는 함수 외부의 변수에 저장될 수 있으므로 탈출 클로저입니다.
func appendClosure(closure: @escaping VoidVoidClosure) {
	// 전달인자로 전달받은 클로저가 함수 외부의 변수 내부에 저장되므로 함수를 탈출합니다.
    closures.append(closure)
}

위 코드에서 두 함수의 전달인자로 전달하는 클로저 앞에 @escaping 키워드를 사용하여 탈출 클로저임을 명시해주어야 합니다. 이 코드는 클로저 모두가 탈출할 수 있는 조건이 명확하기 때문에 @escaping 키워드를 사용하여 탈출 클로저임을 명시하지 않으면 컴파일 오류가 발생합니다.

 

 

* 클로저의 탈출 조건

1) 함수 외부로 다시 전달되어 외부에서 사용이 가능

2) 외부 변수에 저장 가능

 

 

탈출 클로저임을 명시한 경우에는 self 키워드를 사용해 주세요

타입 내부 메서드의 매개변수 클로저에 @escaping 키워드를 사용하여 탈출 클로저임을 명시한 경우, 클로저 내부에서 해당 타입의 프로퍼티나 메서드, 서브스크립트 등에 접근하려면 self 키워드를 명시적으로 사용해야 합니다.

비탈출 클로저는 클로저 내부에서 타입 내부의 프로퍼티나 메서드, 서브스크립트 등에 접근할 때 self 키워드를 쓰지 않아도 괜찮습니다.

 

클래스 인스턴스 메서드에 사용되는 탈출, 비탈출 클로저

func functionWithNoEscapeClosure(closure: (() -> Void)) {
	closure()
}

func functionWithEscapingClosure(
	completionHandler: @escaping () -> Void
) -> (() -> Void) {
	return completionHandler
}

class SomeClass {
	var x = 10
    
    func runNoEscapeClosure() {
    	// 비탈출 클로저에서 self 키워드 사용은 선택 사항입니다.
        functionWithNoEscapeClosure {
        	x = 200
        }
    }
    
    func runEscapingClosure() -> (() -> Void) {
    	// 탈출 클로저에서는 명시적으로 self를 사용해야 합니다.
        return functionWithEscapingClosure {
        	self.x = 100
        }
    }
}

let instance: SomeClass = SomeClass()
instance.runNoEscapeClosure()
print(instance.x) // 200

let returnedClosure: (() -> Void) = instance.runEscapingClosure()
returnedClosure()
print(instance.x) // 100

 

 

출처: 야곰 / 스위프트 프로그래밍: Swift 5

(https://product.kyobobook.co.kr/detail/S000001810190)