Closures : 코드 내에 전달되거나 사용될 수 있는 기능을 포함한 self-contained blocks

Closing over constants/variables : Closure가 자신이 정의된 context 의 constants/variables 을 capture 하고 참조를 저장할 수 있는 것

Swift 는 capture 된 것들의 메모리 관리를 대신 처리해줌

클로저는 아래의 세 개의 형태 중 하나를 띔

Closure Expressions

Full 정의와 이름 없이 함수의 기능을 하는 짧은 버전의 무언가를 작성하는 것이 유용한 경우가 있음. 특히 함수/메서드가 하나 이상의 argument 로 함수를 받을 때.

Closure expressions : inline closure 를 간결한 문법으로 작성할 수 있는 방법. 의도가 분명히 드러나면서도 간결하게 작성할 수 있는 syntax optimization 을 제공함.

The Sorted Method

e.g. sorted(by:) 메서드는 배열을 사용자가 제공한 sorting closure 를 바탕으로 결과를 연산함

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// normal function
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Closure Expression Syntax

{ (<#parameters#>) -> <#return type#> in
   <#statements#>
}

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

Inferring Type From Context

Closure 가 method 의 argument 로 전달되었기 때문에 Swift 는 parameter 의 type 과 return type 을 추론할 수 있음.

sorted(by:) method 가 string 배열에서 호출되고 있으니 argument 는 (String, String) -> Bool 의 function type 을 가져야 함을 추론. 따라서 (String, String), Bool 은 closure expression 의 정의 부분에 써지지 않아도 됨

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

Closure 를 함수/메서드에 inline closure expression 으로 전달할 경우 항상 parameter / return type 을 추론할 수 있음. 따라서 inline closure 를 작성할 때 closure 가 function/method 의 argument 으로 사용된다면 full form 으로 작성하지 않아도 됨

Implicit Returns from Single-Expression Closures

Single-expression closure 는 해당 expression 의 결과를 return 키워드 없이 암묵적으로 return 가능

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

Shorthand Argument Names

Swift 는 inline closure 에 자동으로 shorthand argument name 을 제공함. ($0, $1, $2, …)

Shorthand argument name 을 사용할 경우 closure 의 argument list 를 정의부분에서 뺄 수 있음.

reversedNames = names.sorted(by: { $0 > $1 } )

Operator Methods

Swift 의 String type 은 greater-than operator > 를 두 개의 String parameter 를 받고, Bool 을 return 하는 메서드로 구현했고, 이는 sorted(by:) method 가 필요한 function type 과 일치하기 때문에 단순히 operator 를 전달할 수 있음

reversedNames = names.sorted(by: >)

Trailing Closures

함수의 마지막 argument 로 closure expression 을 전달해야 하고 closure expression 이 긴 경우, trailing closure 로 작성할 수 있음. Trailing closure 가 함수의 argument 임에도 불구하고 trailing closure 는 함수 호출의 괄호 다음에 쓸 수 있음.

Trailing closure 문법에서는 첫 번째 closure 의 argument label 을 쓰지 않음.

Closure 가 길지만 한 줄로 inline 으로 작성하기 어려울 때 유용.

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
}


// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})


// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

// sorted(by:) 를 trailing closure 로 작성한 버전
reversedNames = names.sorted() { $0 > $1 }

// closure expression 이 함수의 유일한 argument 이고 trailing closure 로 작성할 때 () 를 붙이지 않아도 됨
reversedNames = names.sorted { $0 > $1 }

함수가 여러 closure 를 받는 경우 첫 번째 trailing closure 의 arguement label 을 뺌. 아래 예시와 같이 함수를 두 개의 completion handler 를 받게 할 경우 분명하게 의도별로 code 를 나눌 수 있음

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}

Completion handler 는 여러 handler 를 nesting 해야 할 때 가독성이 떨어지므로 이런 경우 asynchronous code 를 쓰는 것이 대안

Capturing Values

Closure 는 정의된 context 내의 constant/variable 을 capture 할 수 있음. Closure 는 해당 constant/variable 을 정의한 scope 가 더 이상 존재하지 않더라도 body 내에서 해당 constant/variable 을 참조하고 수정할 수 있음

e.g. Closure 가 value 를 capture 한 가장 간단한 형태 : nested function. Nested function 은 outer function 의 argument, 정의된 constant/variable 을 capture 할 수 있음

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    
    return incrementer
}


let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()

incrementer()runningTotal, amount 의 reference 를 capture 해서 makeIncrementer 가 끝나더라도 incrementer 가 호출되더라도 runningTotal 이 available 하도록 보장함

최적화로 Swift 는 closure 에 의해 값이 수정되지 않았고, closure 가 생성된 이후에 값이 수정되지 않은 경우 그 값을 capture 하고 복사본을 저장함. (mutable variable 에 대한 reference 를 tracking 하는 것보다 copy 가 가끔 더 빠르고 안전할 수 있기 때문)

var x = 10

let closure = {
    print(x)
}

x = 20
closure() // prints 20

// Closure 는 x 를 copy 하지 않고 variable 자체를 capture

let x = 10

let closure = {
    print(x)
}

// 이 경우 x 는 변하지 않았기 때문에 copy(10) 을 저장함

이게 왜 최적화냐?

Closures Are Reference Types

Functions, closure 는 reference types.

Closure 를 constant/variable 에 할당할 때 실제로는 그 constant/variable 를 function / closure 의 reference 로 삼는 것.

// 새로운 상수를 할당했지만 같은 closure 를 참조하고 있음
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

incrementByTen()
// returns a value of 60

Escaping Closures

Closure 는 closure 가 함수의 argument 로 전달되었지만, 함수가 return 된 이후에 호출될 때 실행될 때 escape a function 이라고 함. Closure 가 escape 되는 것을 허용하는 것을 나타내기 위해 parameter type 앞에 @escaping 을 작성.

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

// @escaping 키워드가 없을 경우 compile-time error
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

self 를 참조하는 escaping closure 는 self 가 class 의 인스턴스를 참조하는 경우 주의가 필요. Escaping closure 에서 self 를 capture 하는 것은 강한 참조를 만들기 쉬움.

Closure 는 일반적으로 body 에서 variable 을 사용해서 암묵적으로 capture 를 하지만, 위의 경우 명시적으로 표기해야 함. self 를 capture 하고 싶은 경우 self 를 명시적으로 쓰거나 closure 의 capture list 에 self 를 포함.

self 를 명시적으로 작성하면 의도를 나타낼 수 있고 reference cycle 이 있는지 확인해야 함을 명시적으로 드러냄.

// nonescaping closure 이기 때문에 self 를 암묵적으로 참조할 수 있음
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
	    // self 없을 경우 compile-time error
        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() {
	    // closure 의 capture list 에 추가
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

self 가 struct / enum 의 인스턴스인 경우 항상 self 를 암묵적으로 참조할 수 있지만 escaping closure 는 self 의 struct / enum 의 mutable reference 는 암묵적으로 참조할 수 없음. Struct, enum 은 shared mutability 를 허용하지 않기 때문

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // OK
        someFunctionWithEscapingClosure { x = 100 }     // Error. self 를 붙이든 안 붙이든 상관 없음
    }
}

Autoclosures

Autoclosure 는 함수에 argument 로 전달된 expression 을 wrap 하기 위해 자동으로 생성된 closure. Argument 를 받지 않고, 호출됐을 때 wrapping 한 expression 의 값을 return. 이는 명시적인 closure 대신에 일반적인 표현을 써서 함수의 parameter 주변의 괄호를 제거할 수 있는 문법적인 이점을 제공함

Autoclosure 를 받는 함수를 호출하는 것은 흔하지만 해당 함수를 구현하는 것은 흔하지 않음.

Autoclosure 는 evaluation 을 연기시켜주는데, 내부 코드는 closure 를 호출하기 전까지 실행되지 않기 때문. Delaying evalution 은 코드가 언제 evaluated 되는지를 제어할 수 있게 해주기 때문에 연산적으로 부하가 있을 때나 side effect 가 있는 코드에 유용함.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5".

// expression is not evaluated yet
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5".

// 실제로 closure 가 호출되기 전까지는 원소가 제거되지 않음
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4".

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

// autoclosure 로 마킹해서 explicit closure 대신에 autoclosure 를 받을 수 있게 함.
// closure 대신에 String argument 를 받는 것처럼 호출 가능
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

Autoclosure 를 escape 할 수 있게 하고 싶으면 @autoclosure, @escaping attribute 를 같이 쓸 수 있음

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))


print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"