Swift @escaping 및 완료 처리기


98

Swift의 'Closure'를 좀 더 정확하게 이해하려고 노력하고 있습니다.

그러나 @escaping하고 Completion Handler이해하기 너무 어렵다

많은 Swift 게시물과 공식 문서를 검색했지만 여전히 충분하지 않다고 느꼈습니다.

이것은 공식 문서의 코드 예제입니다

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

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

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

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

두 가지 방법과 이유가 있다고 들었습니다. @escaping

첫 번째는 클로저를 저장하기위한 것이고 두 번째는 비동기 운영을위한 것입니다.

다음은 내 질문입니다 .

첫째로, 만약 doSomething이 실행 후 someFunctionWithEscapingClosure고정 파라미터로 실행되며, 그 폐쇄 전역 변수 어레이에 저장한다.

클로저는 {self.x = 100}이라고 생각합니다.

self전역 변수에 저장된 {self.x = 100}에서 어떻게 그 객체에 completionHandlers연결할 수 있습니까?instanceSomeClass

둘째, 이렇게 이해 someFunctionWithEscapingClosure합니다.

completionHandler전역 변수 'completionHandlers we using@escaping ' 키워드에 지역 변수 클로저를 저장하려면 !

@escaping키워드 someFunctionWithEscapingClosure반환이 없으면 지역 변수 completionHandler가 메모리에서 제거됩니다.

@escaping 그 폐쇄를 기억에 유지하는 것입니다.

이게 옳은 거니?

마지막으로이 문법의 존재가 궁금합니다.

아마도 이것은 매우 초보적인 질문 일 것입니다.

특정 기능 후에 일부 기능을 실행하려는 경우. 특정 함수 호출 후에 그냥 함수를 호출하지 않는 이유는 무엇입니까?

위의 패턴을 사용하는 것과 이스케이프 콜백 함수를 사용하는 것의 차이점은 무엇입니까?

답변:


122

신속한 완료 처리기 이스케이프 및 비 이스케이프 :

Bob Lee가 자신의 블로그 게시물 Completion Handlers in Swift with Bob에서 다음 같이 설명합니다 .

사용자가 앱을 사용하는 동안 업데이트한다고 가정합니다. 완료되면 사용자에게 확실히 알리고 싶습니다. "축하합니다. 이제 완전히 즐길 수 있습니다!"라는 상자를 표시 할 수 있습니다.

그렇다면 다운로드가 완료된 후에 만 ​​코드 블록을 어떻게 실행합니까? 또한 뷰 컨트롤러를 다음 컨트롤러로 이동 한 후에 만 ​​특정 개체를 어떻게 애니메이션합니까? 음, 우리는 보스처럼 디자인하는 방법을 알아볼 것입니다.

내 방대한 어휘 목록을 기반으로 완료 핸들러는

일이 끝나면 물건을

Bob의 게시물은 완료 핸들러에 대한 명확성을 제공합니다 (개발자 관점에서 우리가 이해해야하는 것을 정확히 정의합니다).

@escaping 클로저 :

함수 인수에 클로저를 전달할 때 함수의 본문이 실행 된 후이를 사용하고 컴파일러를 다시 반환합니다. 함수가 종료되면 전달 된 클로저의 범위가 존재하고 클로저가 실행될 때까지 메모리에 존재합니다.

함수 포함에서 클로저를 이스케이프하는 방법에는 여러 가지가 있습니다.

  • 저장소 : 전역 변수, 속성 또는 호출 함수 이전의 메모리에 존재하는 다른 저장소에 클로저를 저장해야 할 때 실행되고 컴파일러를 다시 반환합니다.

  • 비동기 실행 : despatch 큐에서 비동기식으로 클로저를 실행할 때 큐는 클로저를 메모리에 보관하고 나중에 사용할 수 있습니다. 이 경우 클로저가 언제 실행되는지 알 수 없습니다.

이러한 시나리오에서 클로저를 사용하려고하면 Swift 컴파일러가 오류를 표시합니다.

오류 스크린 샷

이 주제에 대한 자세한 내용 은 Medium에 대한이 게시물을 확인하십시오 .

모든 iOS 개발자가 이해해야하는 점을 하나 더 추가합니다.

  1. 이스케이프 클로저 : 이스케이프 클로저는 함수가 반환 된 후에 호출되는 클로저입니다. 즉, 전달 된 함수보다 오래갑니다.
  2. 비 이스케이프 클로저 : 전달 된 함수 내에서, 즉 반환되기 전에 호출되는 클로저입니다.

@shabhakar, 클로저를 저장했지만 나중에 호출하지 않으면 어떨까요? 또는 메서드가 두 번 호출되었지만 클로저를 한 번만 호출 한 경우. 결과가 동일하다는 것을 알고 있기 때문입니다.
user1101733 jul.

@ user1101733 클로저 이스케이프에 대해 이야기하고 있다고 생각합니다. 클로저는 전화하지 않을 때까지 실행되지 않습니다. 위의 예에서 doSomething 메서드를 2 번 호출하면 2 개의 완료 Handler 객체가 completeHandlers 배열에 추가됩니다. completeHandlers 배열에서 첫 번째 객체를 가져와 호출하면 실행되지만 executionHandlers 배열 수는 동일하게 유지됩니다 (2).
Deepak

@Deepak, 탈출 폐쇄에 대해 예. 가장 최근의 호출을 실행하기 때문에 배열을 사용하지 않고 일반 변수를 사용하여 클로저 참조를 저장한다고 가정합니다. 호출하지 않는 이전 폐쇄가 일부 메모리를 차지합니까?
user1101733

1
@ user1101733 클로저는 참조 유형 (클래스와 같은)입니다. 변수에 새 클로저를 할당하면 속성 / 변수가 새 클로저를 가리 키므로 ARC가 이전 클로저에 대한 메모리 할당을 해제합니다.
Deepak

28

다음은 @escaping이 어떻게 작동하는지 상기시키기 위해 사용하는 몇 가지 예제입니다.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
이 코드는 올바르지 않습니다. @escaping예선 이 없습니다 .
Rob

나는 이것을 가장 좋아했다i.e. escape the scope of this function.
Gal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.