파라미터 등으로 전달할수 있는 자체 코드를 가지고 있는 블럭
캡쳐 : 클로저를 감싸고 있는 블럭의 변수 상수 등 컨텍스트의 범위의 값을 독립적으로 참조할 수 있다.
⇒ 클로저를 감싸고 있는 블럭의 메모리가 해제되어도 클로저는 해당 값,변수를 참조할 수 있다.
전역(global), 중첩?(nested) 함수도 클로저의 하나다
전역 함수는 이름을 가지고 있지만, 외부의 어떤 참조, 값도 없는 클로저
중첩 함수는 이름을 가지고 있고, 블럭을 감싸는 외부의 참조, 값을 캡쳐하고 있는 클로저
클로저의 기본 표현식 이름이 없는 무명 함수에 함수 블럭이 선언된 블럭의 값을 캡쳐하고 있는 상태
Nested Function: 함수의 내부에 자체 코드블럭을 가지고 있는 함수( ⇒ inner function?)
간결하고 집중된 구문으로 의도와 간결함을 유지하여 최적화할 수 있는 인라인 클로저를 작성하는 방법
Swift 표준 라이브러리가 제공하는 함수 중 sorted(by:)
함수는 Collection을 순회하며
사용자가 by
파라미터로 넘긴 구문을 기준으로 엘리먼트를 정렬한 결과 값을 리턴한다.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2:String) {
return s1 > s2
}
names.sorted(by: backward)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
{ (parameter) -> return_type in
// code block
}
코드 블럭은 in
키워드로 시작
⇒ 클로저의 코드 블럭은 짧아야 하기 때문에 in
키워드를 기준으로
앞은 파라미터와 리턴 타입,
뒤는 본문을 작성한다.
names
의 sorted(by)
메소드는 (String
, String) -> Bool
으로 메소드가 정의 되어 있다.
타입을 유추할 수 있을 때 명시적으로 쓰지 않아도 타입 추론으로 생략이 가능하다.
파라미터와 리턴 데이터 타입을 추론할 수 있을 떄 메소드의 리턴 타입을 표현하는 →
도 생략 가능하다.
names.sorted(by: {s1, s2 in return s1 > s2 })
별도의 로직이 없는 단일 표현식 클로저는 암시적으로 리턴 타입을 추론하므로 생략이 가능하다
names.sorted(by: s1, s1 in return s2 > s1 })
swift는 인라인 클로저에 파라미터의 선언을 생략할 수 있다.
생략된 파라미터의 이름은 컴파일러의 추론을 통해 결정된다. ($0, $1, ...)
메소드 선언부와 코드 블럭을 구분하는in
키워드도 생략할 수 있다.
names.sorted(by: {$0 > $1})
메소드의 파라미터와 리턴타입을 만족할 수 있는 연산자(operator)가 있다면 모든 것을 생략하고 해당 연산자만으로 클로저를 대신할 수 있다.
names.sorted(by:>)
// names.sorted(by: {s1, s2 -> Bool in s2 > s1 })
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
클로저는 참조다
call by reference
클로저가 arguments로 넘겨줄 때 클로저를 이스케이프(escape)했다고 표현하고, 함수가 반환(종료)되고 나서 호출 된다.
함수 선언부의 arguments로 받을 때 파라미터의 타입 앞에 @escaping
키워드를 붙여 클로저의 이스케이핑을 허용한다.
→ 클로저의 구현된 로직은 함수의 외부에 있다.
비동기 호출할 때 completion handler
를 파라미터로 전달하면, 함수가 끝날 때 호출되므로, 클로저의 레퍼런스를 파라미터로 전달하여 함수와 독립적으로 비동기 연산이 종료된 후에 호출할 수 있다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosuer(completetionHandler: @escaping () -> Void) {
completionHandlers.append(completetionHandler)
}
someFunctionWithEscapingClosure
를 arguments로 받아 전역에 선언된 배열에 추가하는 로직이다.
@escaping
키워드를 completionHandler에 붙이지 않는다면 컴파일 에러가 발생한다.
이스케이핑 클로저가 self
를 참조할 경우 주의해야 한다.
캡쳐된 self
와 이스케이핑 클로저가 강한 참조 사이클을 만들 수 있다.
이스케이핑 클로저는 struct나 enumeration가 self 일 때 ⇒ enumeration, struct의 이스케이핑 클로저일 때 변경 할 수 있는(mutable) 객체를 참조 할 수 없다. (struct와 enumeration은 value)
클로저 형식으로 선언된 형식으로 생성되어 메소드 파라미터로 전달되는 클로저
중요한 특징으로
오토클로저는 호출되기 전에 코드가 실행되지 않기 때문에 미리 평가(evaluation)되지 않는다.
Delaying evenaluation은 사이드 이펙트를 가지는 코드 사용에 용이하고, 그렇게 때문에 계산에 비용이 비싸다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
// + remove and return first elements on called
// + not work it before call
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"
오토 클로저를 남용하면 코드의 이해를 떨어뜨릴 수 있다. 사용해야 할 경우 이름에 의도를 고려하여 명확하게 결정한다.
이스케이핑 클로저를 오토클로저에 활용할 경우 @autoclouse @escaping
키워드를 사용한다.
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
// (1)
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
// (2)
for customerProvider in customerProviders {
// (3)
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
위 코드에서 (1) 표기된 customersInLine.remove(at:Int)
호출이 오토 클로저로 collectionCustomerProviders
의 customerProvider
파라미터로 전달된다.
(2)의 루프가 실행되기 전에는 customersInLine.remove(at:Int)
는 실제로 호출되지 않고,
루프가 실행되면 (3)의 코드가 실행 될 때 실제로 customersInLine.remove(at:Int)
코드가 실행된다.