사용자의 코드안에서 전달되어 사용할 수 있는 로직을 가진 중괄호 "{}"로 구분된 코드의 블록, 일급 객체의 역활을 수행
참조 타입이다
함수는 클로저의 한 형태로, 이름이 있는 클로저!!
일급객체??
일급 객체는 전달인자를 보낼 수 있고, 변수/상수 등으로 저장하거나 전달할 수 있으며, 함수의 반환값으로도 될 수 있다.
{ (인자들) -> 반환타입 in
로직구현
}
코드를 효율적으로 작성하는데 도움을 주기 떄문에
대표적으로 두가지가 있을 수도 있겠다.
고차함수??
인풋으로 함수를 받을 수 있는 함수
대표적은 map, filter
클로저를 통해 비동기 콜백을 작성하는 경우, 현재 상태를 미리 획득(저장)하지 않으면 실제로 클로저의 기능을 실행하려는 순간, 상수나 변수가 이미 메모리에 존재하지 않을 수도 있다.
따라서 이 떄 필요한 것이 바로 클로저의 값 갭쳐
기능이다.
말 그대로 값을 캡쳐해서 사용하는 것을 말한다.
값을 캡쳐해서 가지고 있다가, 필요할 때마다 사용하는 것
클로저가 함수의 인자로 전달되지만 함수 밖에서 실행되는 것(함수가 반환된 후 실행 되는 것)
func runClosure(closure: () -> Void) {
closure()
}
클로저가 실행되는 순서
1. 클로저가 runClosure()
함수의 closure
인자로 전달됨
2. 함수 안에서 closure()
가 실행됨
3. runClosure()
함수가 값을 반환하고 종료됨
이렇게 클로저가 함수가 종료되기 전 실행되기 때문에 closure
는 Non-Escaping 클로저!!
class ViewModel {
var completionhandler: (() -> Void)? = nil
func fetchData(completion: @escaping () -> Void) {
completionhandler = completion
}
}
이스케이핑 클로저가 실행되는 순서
1. 클로저가 fetchData()
함수의 completion
인자로 전달
2. 클로저 completion
이 completionhandler
변수에 저장됨
3. fetchData()
함수가 값을 반환하고 종료됨
4. 클로저 completion
은 아직 실행되지 않음
completion
은 함수의 실행이 종료되기 전에 실행되지 않기 때문에, escaping 클로저, 다시 말해 함수 밖(escaping)에서 실행되는 클로저
사용 예
비동기로 실행되는 HTTP Request CompletionHandler
func makeRequest(_ completion: @escaping (Result<(Data, URLResponse), Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "http://jusung.github.io/")!) { data, response, error in
if let error = error {
completion(.failure(error))
} else if let data = data, let response = response {
completion(.success((data, response)))
}
}
}
makeRequest()
함수에서 사용되는 completion
클로저는 함수 실행 중 즉시 실행되지 않고, URL 요청이 끝난 후 비동기로 실행 됨.
이 경우에도 competion
의 타입에 @escaping
을 붙여서 escaping
클로저라는 것을 명시 해줘야 함
보통 클로저가 다른 변수에 저장되어 나중에 실행되거나 비동기로 실행될 때 escaping 클로저가 사용됨
공식문서의 예제를 보자
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 한다.
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) // 200
completionHandlers.first?()
print(instance.x) // 100
자동 클로저는 인자 값이 없으며 특정 표현을 감싸 다른 함수에 전달 인자로 사용할 수 있는 클로저를 말함. 자동 클로저는 클로저를 실행하기 전 실제 실행되지않는다
즉 계산이 복잡한 연산을 하는데 유용..
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count) // 5
let customerProvider = { customersInLine.remove(at: 0) } // 해당 코드가 지나도 count가 줄지 않는다.
print(customersInLine.count) // 5
// customerProvider가 실행되었을때만 동작
print("Now serving \(customerProvider())!") // "Now serving Chris!"
print(customersInLine.count) // 4
자동클로저를 함수의 인자 값으로 넣는 예제는 아래와 같음.
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } ) // "Now serving Alex!"
Servo 함수는 인자로 () -> String(인자가 없고, String을 반환하는 클로저)를 가짐.
그리고 이 함수를 실행할 때 serve(customer:{ customersInLine.remove(at:0) } )와 같이 클로저를 명시적으로 직접 넣을 수 있음
@autoclosure 키워드를 이용해서 보다 간결하게 사용할 수 있다.
@autoclosure는 @escaping과 같이 사용할 수 있다.
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0)) // "Now serving Ewa!"
var customersInLine = ["Barry", "Daniella"]
var customerProviders: [() -> String] = [] // 클로저를 저장하는 배열을 선언
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
} // 클로저를 인자로 받아 그 클로저를 customerProviders 배열에 추가하는 함수를 선언
collectCustomerProviders(customersInLine.remove(at: 0)) // 클로저를 customerProviders 배열에 추가
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.") // 2개의 클로저가 추가 됨
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!") // 클로저를 실행하면 배열의 0번째 원소를 제거하며 그 값을 출력
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
정리
클로저는 고차함수와 비동기 콜백에 많이 이용하는데, 그 중에서도 비동기 콜백에 이용되는 이유는 클로저의 값 캡쳐 기능 때문..
값 갭쳐는 값을 미리 획득해서 선언할 때마다 획득한 값의 참조에 연산을 누적하는 것!
참조 사이트
https://seolhee2750.tistory.com/117
https://medium.com/@jgj455/%EC%98%A4%EB%8A%98%EC%9D%98-swift-%EC%83%81%EC%8B%9D-closure-aa401f76b7ce