Closure in Swift - 기능을 가진 코드 블럭
19 Jun 2019 |
Closure in Swift
Closure : 기능을 가진 코드 블록
Self-contained blocks of functionality
Closures can capture and store references to any constants and variable from the context in which they are defined.
클로져는 자신이 정의된 곳의 상수와 변수에 대한 reference 를 capture, store 할 수 있다.
This is known as closing over this constants and variables.
상수와 변수를 가둔다(close over) 는 의미이다.
함수(Functions)도 클로져에 포함되는 개념이다. (클로져 > 함수) 함수는 특별한 클로져 케이스이다.
이름이 있는 클로져를 함수라고 한다.
📎 클로져의 형식 3가지
- Global functions
- 이름을 가짐
- 어떤 value 도 캡쳐 안함
- Nested functions (함수 안에서 정의된 함수)
- 이름 있음
- nested function을 감싸는 function 내부의 value를 캡쳐할 수 있음
func outer() { func inner() { // <- Nested function ... } }
- Closures
- 이름 없음
- 클로저가 선언된 context 주변의 value를 캡쳐할 수 있다
형식 | 이름 | capturing value |
---|---|---|
Global function | 있음 | 안함 |
nested function | 있음 | enclosing function 내부 value |
closure | 없음 | 선언된 곳 context 의 value |
Closure Expressions
Nested function 도 좀 더 큰 function 에서 기능을 갖는 코드 블럭을 네이밍하는 좋은 방법이다.
하지만 선언과 이름 없이 더 짧은 형태의 함수같은 기능을 하는 코드 블럭을 만들고 싶다면 클로져가 좋은 방법이 된다.
Syntax
{ (parmeters) -> return_type in
statements
}
선언부와 구현부를 in
기준으로 구분
Sorted Method
-
Swift standard library 가 제공하는 method
- array 의 value 들을 전달받은 sorting closure 기준으로 정렬하여, 정렬된 새로운 배열을 return 해줌.
-
원래 배열은 변동 없음
- (higher order function)
- 인자로 받는 클로져가 판단하는 바
- 첫번째 값이 두번째 값보다 먼저 나오면 true
- ascending (s1 < s2) , descending(s1 > s2)
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = name.sorted(by: backward)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
var reversedNamesUsingClosure = name.sorted(by: {
(s1: String, s2: String) -> Bool in
return s1 > s2
})
-
Inferring Type from Context
- array element 를 인자로 받는 클로져니까, Swift가 Inferring Type 가능
- String 생략 가능
reversedNames = name.sorted(by: { (s1, s2) -> Bool in return s1 > s2})
-
Single-Expression Closure 일 경우, return 생략 가능
- 클로져 바디(
in
뒷부분)가 한줄이면, implicitly return 한다고 판단함
reversedNames = name.sorted(by: { (s1, s2) -> Bool in s1 > s2})
- 클로져 바디(
-
Shorthand Argument Names
- 인자 이름도 지정 안하고
$0
처럼 사용가능 - 이름 선언 안하면,
in
keyword 도 필요 없음
reversedNames = name.sorted(by: { $0 > $1 })
- 인자 이름도 지정 안하고
-
Operator Methods
>
operator : 지정 연산자로String
type 에 구현되어 있음>
자체가 사실은 method 이므로, 부등호만 인자로 넘겨도 가능
reversedNames = name.sorted(by: >)
Trailing Closure
함수의 마지막 인자로 클로져를 넘기는 경우, 클로져를 굳이 ()
(argument list) 안에 쓰지 않아도 됨
-
괄호 뒤부터
{}
사용하기reversedNames = name.sorted() { $0 > $1 }
-
인자가 클로져 단독이라면,
()
도 생략 가능reversedNames = name.sorteds{ $0 > $1 }
-
클로져 바디가 긴 경우에 가독성 높일 수 있음
-
Example : Swift Array
map(_:)
method- 숫자 받아서 각 자리에 맞는 string 합쳐서 반환하기
- 일의 자리부터 (%10) (뒤에서 앞으로)
let digitNames = [ 0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine" ] let numbers = [16, 58, 510] let numberStrings = numbers.map { (num) -> String in var num = num var output = "" repeat { output = digitNames[num % 10]! + output num = num / 10 } while num > 0 return output } //["OneSix", "FiveEight", "FiveOneZero"]
- dictionary lookup with subscript returns optional → need exclamation mark(
!
)
Capturing Value
A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
closure 는 정의된 곳의 surrouding context 에 있는 variable & constant 를 capture 한다.
클로져 안에서 그 변수, 상수의 값을 참조하고, 수정할 수 있다.
original scope 이 더 이상 존재하지 않아도 클로져 안에서 사용 가능 → 그래서 capture 한다고 함
-
nested function : swift에서 클로져가 capture value 하는 대표적인 예
-
nested function 은 outer function의 인자, 변수 상수 다 capture 가능
-
Example : 주어진 수 만큼, increment 되도록 하는 함수를 반환
- outer function : 함수 만들기
- nested function : amount 만큼 증가시킨 수를 반환
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }
- nested function(incrementer()) 는 outer function 의
runningTotal
변수를 capture runningTotal
변수를 참조하고 그 값을 amount 만큼 증가시킴makeIncrementer
함수가 return 하고 종료해도 반환된 함수는 여전히runningTotal
을 참조하고 값을 바꿀 수 있음
let incrementByTen = makeIncrementer(forIncrement: 10) incrementByTen() // 10 incrementByTen() // 20 incrementByTen() // 30 let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // 7 incrementByTen() // 40
- 생성될 될 때마다 새로운
runningTotal
을 참조하는 것이므로incrementBySeven
은incrementByTen
과는 다른 변수를 참조함
Closures Are Reference Types
caputuring value 가 가능한 이유 : closure가 reference type 이기 때문
클로져나 함수를 어떤 variable or constant 에 할당하는 것은 그 reference를 할당하는 것
-
앞의 예에서
incerementByTen
은 클로져 content 그 자체를 가지고 있는게 아니고 reference 만 가지고 있는 것 -
서로 다른 변수나 상수가 같은 closure 를 참조할 수 있음
let anotherReference = incrementByTen anotherReference() // 50
Escaping Closure
function 의 인자로 넘겨진 클로져가, 그 function이 return 된 후에 호출(call)될 때, 함수를 escape 한다(escape a function) 라고 함
-
escape 하는 인자 앞에
@escaping
붙이면 됨 -
function 밖에서 선언된 변수에 클로져가 저장되는 경우 - escape function 하는 예
var completionHandlers: [() -> Void] = [] func functionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }
- function 밖에서 선언된 변수
completionHandlers
에 인자로 받은 클로져가 저장된다. - 이 클로져는 인자로 넘겨진 함수가 return 된 후에도 사용되어야 하므로
@escaping
필요 - 명시 안하면 컴파일 에러 남
- function 밖에서 선언된 변수
-
@escaping
를 명시해준다 == 클로져에서self
를 명시적(explicitly)으로 참조한다.var completionHandlers: [() -> Void] = [] func functionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } func functionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { functionWithEscapingClosure { self.x = 100 } functionWithNonescapingClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first?() print(instance.x) // Prints "100"
functionWithNonescapingClosure
는self
를 암묵적으로 참조한다.- 클로져에서
self
를 사용한다면@escaping
필요
Autoclosures
An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function.
-
function argument 로 넘길 클로져를 자동으로 만들어 줌 (
{}
없이 넘겨도 클로져가 됨) -
함수 인자로 넘기는 부분에 표현을 직접 써 넣는 방법
-
autoclosure 는 argument 없고, 호출시 내부 표현 값을 return 한다 → single-line expression 가능
-
autoclosure 를 인자로 받는 함수를 호출하는 건 흔함
-
그런 함수를 구현하는 건 안 흔함.
-
@autoclosure
keyword 명시 -
Example
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5" let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // Prints "5" print("Now serving \(customerProvider())!") // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4"
- customerProvider 호출 되기 전까진 배열 요소 줄어들지 않음
- serving 하는 부분을 담은 함수를 만들기
// 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 를 받는다고 허용하면
{}
도 필요 없음
// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) //autoclosure // Prints "Now serving Ewa!"
- autoclosure, escaping 같이 사용가능
// 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!"