클로저 (Closures) - 탈출 클로저 (Escaping Closures)

00yhsp·2024년 4월 8일

함수에 인수로 클로저를 전달하지만 함수가 반환된 후 호출되는 클로저를 함수를 탈출 (escape) 하다 라고 말한다. 클로저를 파라미터로 갖는 함수를 선언할 때 이 클로저는 탈출을 허락한다는 의미로 파라미터의 타입 전에 @escaping 을 작성할 수 있다.

클로저가 탈출할 수 있는 한 가지 방법은 함수 바깥에 정의된 변수에 저장되는 것이다. 예를 들어 비동기적 작업을 시작하는 대부분의 함수는 완료 핸들러로 클로저를 사용합니다. 이 함수는 작업을 시작한 후에 반환되지만 작업이 완료될때까지 클로저가 호출되지 않는다. 클로저는 나중에 호출하려면 탈출해야 합니다. 예를 들어:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:) 함수는 인수로 클로저를 가지고 있고 함수 바깥에 선언된 배열에 추가한다. 함수의 파라미터에 @escaping 을 표시하지 않으면 컴파일 시 에러가 발생한다.

self 를 참조하는 이스케이프 클로저는 self 가 클래스의 인스턴스를 참조하는 경우 특별한 고려가 필요하다. 이스케이프 클로저에 self 캡처는 강한 참조 사이클이 생기기 쉽다.

일반적으로 클로저는 클로저 내부에서 변수를 사용하여 암시적으로 변수를 캡처하지만 이 경우에는 명시적이어야 한다. self 를 캡처하려면 사용할 때 명시적으로 self 를 작성하거나 클로저의 캡처 리스트에 self 를 포함한다. self 를 명시적으로 작성하는데 의도를 표현하고 참조 사이클이 없음을 확인하도록 상기시켜 준다. 예를 들어 아래 코드에서 someFunctionWithEscapingClosure(:) 에 전달된 클로저는 명시적으로 self 를 참조한다. 반대로 someFunctionWithNonescapingClosure(:) 에 전달된 클로저는 비이스케이프 클로저이다. 즉 암시적으로 self 를 참조할 수 있다.

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

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

다음은 클로저의 캡처 리스트에 포함하여 self 를 캡처하고 암시적으로 self 를 참조하는 doSomething()이다.

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

self 가 구조체 또는 열거형 인스턴스이면 항상 암시적으로 self 를 참조할 수 있다.
그러나 이스케이프 클로저는 구조체 또는 열거형 인스턴스이면 self 에 대한 변경 가능한 참조를 캡처할 수 없다.
구조체와 열거형은 공유 변경을 허용하지 않는다.

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

위의 예제에서 someFunctionWithEscapingClosure 함수 호출은 변경 가능한 메서드 내부에 있기 때문에 에러이고 self 는 변경 가능하다. 이것은 이스케이프 클로저는 구조체인 self 를 변경가능한 참조로 캡처할 수 없다는 규칙을 위반한다.

profile
iOS Dev

0개의 댓글