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
연결할 수 있습니까?instance
SomeClass
둘째, 이렇게 이해 someFunctionWithEscapingClosure
합니다.
completionHandler
전역 변수 ‘completionHandlers we using
@escaping ‘ 키워드에 지역 변수 클로저를 저장하려면 !
@escaping
키워드 someFunctionWithEscapingClosure
반환이 없으면 지역 변수 completionHandler
가 메모리에서 제거됩니다.
@escaping
그 폐쇄를 기억에 유지하는 것입니다.
이게 옳은 거니?
마지막으로이 문법의 존재가 궁금합니다.
아마도 이것은 매우 초보적인 질문 일 것입니다.
특정 기능 후에 일부 기능을 실행하려는 경우. 특정 함수 호출 후에 그냥 함수를 호출하지 않는 이유는 무엇입니까?
위의 패턴을 사용하는 것과 이스케이프 콜백 함수를 사용하는 것의 차이점은 무엇입니까?
답변
신속한 완료 처리기 이스케이프 및 비 이스케이프 :
Bob Lee가 자신의 블로그 게시물 Completion Handlers in Swift with Bob에서 다음 과 같이 설명합니다 .
사용자가 앱을 사용하는 동안 업데이트한다고 가정합니다. 완료되면 사용자에게 확실히 알리고 싶습니다. “축하합니다. 이제 완전히 즐길 수 있습니다!”라는 상자를 표시 할 수 있습니다.
그렇다면 다운로드가 완료된 후에 만 코드 블록을 어떻게 실행합니까? 또한 뷰 컨트롤러를 다음 컨트롤러로 이동 한 후에 만 특정 개체를 어떻게 애니메이션합니까? 음, 우리는 보스처럼 디자인하는 방법을 알아볼 것입니다.
내 방대한 어휘 목록을 기반으로 완료 핸들러는
일이 끝나면 물건을
Bob의 게시물은 완료 핸들러에 대한 명확성을 제공합니다 (개발자 관점에서 우리가 이해해야하는 것을 정확히 정의합니다).
@escaping 클로저 :
함수 인수에 클로저를 전달할 때 함수의 본문이 실행 된 후이를 사용하고 컴파일러를 다시 반환합니다. 함수가 종료되면 전달 된 클로저의 범위가 존재하고 클로저가 실행될 때까지 메모리에 존재합니다.
함수 포함에서 클로저를 이스케이프하는 방법에는 여러 가지가 있습니다.
-
저장소 : 전역 변수, 속성 또는 호출 함수 이전의 메모리에 존재하는 다른 저장소에 클로저를 저장해야 할 때 실행되고 컴파일러를 다시 반환합니다.
-
비동기 실행 : despatch 큐에서 비동기식으로 클로저를 실행할 때 큐는 클로저를 메모리에 보관하고 나중에 사용할 수 있습니다. 이 경우 클로저가 언제 실행되는지 알 수 없습니다.
이러한 시나리오에서 클로저를 사용하려고하면 Swift 컴파일러가 오류를 표시합니다.
이 주제에 대한 자세한 내용 은 Medium에 대한이 게시물을 확인하십시오 .
모든 iOS 개발자가 이해해야하는 점을 하나 더 추가합니다.
- 이스케이프 클로저 : 이스케이프 클로저는 함수가 반환 된 후에 호출되는 클로저입니다. 즉, 전달 된 함수보다 오래갑니다.
- 비 이스케이프 클로저 : 전달 된 함수 내에서, 즉 반환되기 전에 호출되는 클로저입니다.
답변
다음은 @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()
}
}
}