본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.
Escaping closure/Defer
둘의 차이를 잘 모르겠어서 조금 공부해봤다.
알아보니 완전 다른 것인..ㅎㅎ^^
escaping closure는 언제 실행될지 확신이 없는 것이고, defer는 함수 종료를 하면서 문 닫고 나가면서 실행하는 것이다.
escaping closure는 언제 실행될지 모르지만 언젠가 불린다는 것이다.
escaping closure
어떤 클로저가 메서드의 인자로 전달 되지만, 메서드가 반환된 후에 불릴 때 이것을 메서드를 escape한다고 말한다. 매개변수로 클로저를 취하는 메서드를 정의할 때, 그 클로저가 escape하게 만들기 위해서 @escaping을 매개변수의 앞에 쓴다.
클로저가 escape할 수 있는 방법 중 하나는 메서드 외부에 정의된 변수에 저장되는 것이다. 예를 들어, 비동기 작업을 시작하는 많은 메서드는 completion handler로 클로저 인자를 취한다. 그 메서드는 작업을 시작한 후에 반환되지만, 클로저는 작업이 완료될 때까지 호출되지 않는다 - 클로저는 escape되어야하며 나중에 호출된다. 예를 들어보면 이렇다:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:) 메서드는 그것의 인자로 클로저를 취한다. 그리고 메서드 외부에서 정의된 배열에 그 클로저를 추가해준다. 만약에 @escaping이라고 매개변수에 써주지 않는다면, 컴파일 에러가 날 것이다.
self를 참조하는 escaping 클로저는, 만약 self가 클래스의 인스턴스를 참조하는 경우, 특별한 고려가 필요하다.
escaping 클로저에서 self를 캡쳐하는 것은 강한 순환 참조를 실수로 만들곤 할 것이다. 강한 순환 참조에 대해 더 알아보고자 한다면, Automatic Reference Counting을 보자.
일반적으로, 클로저는 클로저의 바디 내부에서 사용함으로서 암묵적으로 변수를 캡쳐한다. 하지만, 이런 경우에는 명시적으로 해야한다. 만약 self를 캡쳐하고자한다면, self를 사용하는 곳에서 명시적으로 self를 써야한다, 혹은 클로저의 캡쳐 리스트에 self를 더해야한다. self를 명시적으로 적는 것은 의도를 명확하게 표현하도록 할 수 있으며, 순환 참조가 없다는 것을 확인할 수 있도록 한다. 예를 들어서, 다음의 코드에서, someFunctionWithEscapingClosure(_:)에게 전달 된 클로저는 self를 명시적으로 참조하고 있다. 이와 반대로, someFunctionWithNonescapingClosure(_:)에게 전달 된 클로저는 escaping 클로저가 아니다. 이것은 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"
class SomeOtherClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { [self] in x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
위의 예시에서의 someFunctionWithEscapingClosure의 호출은 에러가 날 것이다. 왜냐하면, 이게 가변적인 메서드 안에 들어있기 때문이다. 그래서 self가 가변적이기 때문이다. escaping 클로저가 구조체의 self에 대한 가변적인 참조를 캡쳐할 수 없다는 룰에 위배된다.
여기까지가 공식 문서에 대한 내용이었다.
정리해보자면..
escaping 클로저를 사용할 때는 @escaping 키워드를 사용해줘야한다.
escaping이 아닌 클로저에 대해서도 그 키워드를 사용해줘도 되는 듯 하지만, 컴파일러의 퍼포먼스와 최적화에 영향을 끼치기 때문에 그렇게 쓰지 않는 듯 하다.
escaping 클로저가 흘러가는 방식은 다음과 같다
다음의 코드를 분석해보자:
final class NetworkManager {
static let shared = NetworkManager()
private let session: URLSession
init(session: URLSession = URLSession.shared) {
self.session = session
}
func startLoad(request: URLRequest, mime: String, completionHandler: @escaping (Result<Data, NetworkError>) -> Void) {
session.dataTask(with: request) { data, response, error in
guard error == nil else {
completionHandler(.failure(.responseError(error: error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completionHandler(.failure(.invalidResponse))
return
}
guard (200...299).contains(httpResponse.statusCode) else {
completionHandler(.failure(.responseCodeError))
return
}
let mimeType = response?.mimeType
guard ((mimeType?.lowercased().contains(mime)) != nil) else {
completionHandler(.failure(.invalidMimeType))
return
}
guard let validData = data else {
completionHandler(.failure(.noData))
return
}
completionHandler(.success(validData))
}.resume()
}
}
이 코드에서는 startLoad에 completionHandler라는 인자로 escaping 클로저가 전달 되었다. 이 escaping 클로저는 Result<Data, NetworkError>를 매개변수로 갖고, 반환값이 없는 클로저이다. startLoad에 Result값을 어떻게 처리해줄 지에 대한 도구를 넣어준 셈이다. 그리고 이 completionHandler라는 놈은 나중에 Result값에 상응하는 놈이 들어오면 실행이 될 것이다. 그것이 escaping 클로저니까.
참조
https://jusung.github.io/Escaping-Closure/
'YAGOM CAREER STARTER' 카테고리의 다른 글
[TIL] 20230411: URL Loading System (0) | 2023.04.14 |
---|---|
[TIL] 20230404: URLSession (0) | 2023.04.05 |
[TIL] 20230327: class/struct, 네트워크 통신 없이 Test하는 이유 (0) | 2023.03.28 |
[TIL] 20230321: Fetching Website Data into Memory (0) | 2023.03.25 |
[TIL] 20230317: Responder Chain / Touch Event (0) | 2023.03.18 |