Out of Bedlam Swiftish

스위프트3에서의 @noescape

스위프트3에서 @noescape가 클러저의 디폴트 속성이 되면서 지원 종료 예정으로 디프리케이트(deprecated)되었습니다. (@noescape에 대한 자세한 사항은 여기 이전 글을 참조) 하지만 여전히 함수를 탈출(escape)가능하도록 작성할지에 대한 고민이 없어진 것은 아니므로 @noescape@escaping이 뜻하는 것이 무엇인지 잘 알아 두어야 합니다.

먼저 함수를 호출할 때 클로저를 전달할 것인지 판단해야하며, 만약 함수가 리턴되어 실행 제어가 넘어온 상태에서도 그 시점 이후로도 파라미터로 전달된 클로저를 호출해야한다면 이 함수는 대부분 탈출 가능하도록 선언(escape)되어야 합니다.

하지만 그 반대로, 함수의 범위내에서만 클로저를 사용하는 경우라면 이전처럼 지저분한 @noescape를 지정하지 않아도 됩니다. 이 클로저는 디폴트로 탈출이 불가능한 클로저이기 때문입니다.

다음과 같이 클로저를 파라미터로 전달 받는 함수를 생각해 보겠습니다. 아래에서 파라미터 completion은 위에서 설명한 것 처럼 이미 탈출이 불가능합니다.

func asyncOp(_ completion:()->Void)
{
  // 어떤 비동기 작업을 수행한 후 최종적으로 completion()을 호출합니다.
}

이전 버전의 스위프트에서는 클로저를 탈출이 가능한 것이 디폴트였기 때문에 탈출을 불가능하도록 만들기위해 별도의 어트리뷰트인 @noescape를 사용했어야하지만 이제는 반대로 클로저가 탈출이 불가능한 것이 드폴트이므로 오히려 명시적으로 탈출 가능하도록 선언하기 위해서 @escaping을 사용해야합니다. 다음 코드는 사용 예입니다.

var onFakeCompletions:[()->()] = []

func fakeNetworkOp(_ completion:@escaping ()->())
{
    onFakeCompletion.append(completion)
}

이렇게 클로저를 디폴트로 탈출불가(non-escape)하게 만듦으로 파생되는 가장 확실한 이점으로는 컴파일러가 코드를 최적화하는 과정에서의 성능향상입니다. 해당 클로저가 탈출할 수 없다는 것은 컴파일러가 더 이상 메모리 관리상의 지저분한 일들에 관여할 필요없다는 뜻이기 때문입니다.

또한, 탈출불가 클로저 내에서 self키워드를 사용할 수 있습니다. 왜냐하면 이 클로저는 해당 함수가 끝나서 리턴되기 전에 호출될 것이 명확하기 때문입니다. 따라서 클로저 내에서 self에 대한 약한 참조(weak reference)를 사용해야할 필요가 없습니다.