Closure(클로저)
는 일종의 실행 가능한 코드블럭이라고 할 수 있습니다. 이름 없는 함수(메소드)라고 합니다. 이전에 공부 한 함수는 Closure
의 형태로 이름이 있는 클로저라 할 수 있습니다. C++에서의 람다와 비슷하다고 이해했습니다.
클로저는 전달 인자로도 넘겨줄 수 있으며, 변수나 상수로 저장될 수 도 있습니다. 그리고 함수의 반환 값으로도 사용할 수 있죠. 그리고 클로저는 레퍼런스 타입입니다.
Closure
//Closure
{ (parameters) -> return type in
//... code
}
var addClosure: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
return a + b
}
클로저 표현 방식입니다. 함수와 비슷한 구조이며 func
키워드가 없고 in
이 있는 차이입니다. in
이전의 내용은 인자, 반환 값의 정의이며 그 이후는 코드가 클로저의 로직 시작입니다. 클로저는 코드 블럭이기 때문에 함수처럼 호출할 수 있는 이름이 존재하지 않습니다. 하지만 addClosure
처럼 변수의 타입으로 클로저 선언이 가능하며 저장할 수 도있습니다.
축약하기
//Closure
var addClosure: (Int, Int) -> Int = { (a, b) in return a + b }
var addClosure: (Int, Int) -> Int = { (a, b) in a + b }
var addClosure: (Int, Int) -> Int = { $0 + $1 } //인자의 순서가 번호로 매겨집니다.
Closure의 타입을 생략할 수도 있습니다. 타입이 명시되어있을 경우에는 이렇게 생략하여서 만들어 줄 수 있습니다. return
역시 생략이 가능합니다(클로저 로직이 한 줄 일 경우). 인자의 이름까지도 생략할 수 있습니다. 표현 방식은 $
을 통해서 할 수 있고, 숫자로 인자 위치를 찾을 수 있습니다. 이것을 단축 인자 이름이라고 합니다.
사용하기
//두 수의 덧셈함수
func operateTwoNumber(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
인자로 받은 클로저를 사용하여 두 수를 계산 후 반환하는 함수입니다. 클로저를 이용하게 되면 하나의 함수로 다양한 계산을 할 수 있습니다. 물론 클로저의 계산 로직이 필요합니다.
//두 수의 덧셈함수
var addClosure: (Int, Int) -> Int = { (a, b) in a + b }
var subClosure: (Int, Int) -> Int = { (a, b) in a - b }
operateTwoNumber(a: 10, b: 5, operation: addClosure)
//결과 : 15
operateTwoNumber(a: 10, b: 5, operation: subClosure)
//결과 : 5
앞에서 만든 operateTwoNumber
와 addClosure
를 이용하면 덧셈을 할 수가 있습니다. 추가로 subClosure
를 만들어 주었습니다. operation의 값으로 전달해주면 우리는 뺄셈을 할 수가 있겠네요.
후행 클로저
let result = operateTwoNumber(a: 10, b: 2){a, b in a / b}
함수의 마지막 전달 인자가 클로저일 때 후행 클로저
를 사용할 수 있습니다. Xcode에서도 자동 완성을 후행 클로저를 우선시해줍니다. 후행 클로저는 클로저가 길어서 한 줄에 작성할 수 없을 때 가장 유용합니다.
클로저 반환하기
func greeting(name: String) -> String{
return "hi, " + name
}
greeting(name: "minmong")
//return type {() -> String} Closure
func greetingClosure(name: String) -> () -> String{
return { () in return "hi, " + name}
}
greetingClosure(name: "minmong")()
//결과 : "hi, minmong"
클로저를 반환하는 함수입니다. 굳이 이렇게 만들어야 할 내용은 아닙니다. 반환하는 클로저 모습을 익히기 위한 것입니다. greeting
함수는 name
을 받아 문자열을 반환해주는 함수입니다. 이것을 클로저를 이용해서 표현해봅시다. greetingClosure
는 반환 타입이 () -> String
인 클로저입니다. 함수 내부의 반환 형태도 클로저 형태가 되어야 합니다. (앞에서 처럼 in, return 생략이 가능합니다.) 그런데 마지막 라인 코드 모양이 좀 특이합니다. 함수 호출 후 ()
가 한 번 더 사용이 되었는데요. 이건 반환 값이 클로저자체기 때문에 클로저 호출을 통해서 내부 로직을 실행시키는 모습입니다.
func greetingClosure(name: String) -> (String) -> String{
return { greet in return "hi, " + greet + " " + name}
}
greetingClosure(name: "minmong")("hello")
//결과 : "hi, hello minomng"
이렇게 클로저 인자가 있을 경우 값을 전달해 주어야 합니다.
Capturing Values (캡처)
클로저는 스코프의 상수와 변수를 캡처할 수가 있습니다. C++ 람다에서도 캡처가 있었는데요. 그거와 유사한 듯합니다. 캡처의 뜻은 쉽게 생각하면 순간을 기억 저장하는 것이죠? 코드에서는 블럭 단위로 나뉘게 됩니다. 예를 들어 함수에서 할당 한 로컬 변수는 함수 외부에서 사용을 할 수가 없게 없습니다. 일반적으로 함수를 사용하고 나면 해당 로컬 변수의 메모리(스택)는 소멸되기 때문입니다. 클로저는 클로저 외부의 상수와 변수를 캡처할 수 있습니다. 외부의 상수와 변수를 사용하기 위해서 참조한다고 생각하면 됩니다.
func capturingClosure(addingValue: Int) -> () -> Int {
var totalValue = 0
return {
totalValue += addingValue
return totalValue
}
}
let addFive = capturingClosure(capturingValue: 5)
addFive() //결과 : 5
addFive() //결과 : 10
addFive() //결과 : 15
captureingClosure
함수 입니다. 반환 타입은 () -> Int
입니다. 함수는 addingValue
인자와 totalValue
로컬 변수가 있습니다. 반환되는 클로저에서는 totalValue에 addingValue를 더하고 있습니다. capturingClosure함수를 호출해봅시다. 값으로는 5를 전달하고 있습니다. addFive
에 클로저를 할당해 줍니다. 여기서 실제 함수 호출은 한 번 일어났습니다.
하지만 addFive를 호출하게 되면 addFive에 참조로 할 당 됩니다. 클로저 내부에서 함수의 로컬 변수들이 캡처되어 5, 10, 15로 증가됨을 볼 수 있습니다. 함수 호출 후 소멸해야 할 변수 메모리가 사라지지 않은 것이죠. 캡처된 변수는 클로저가 실행될 때에 정해진다고 합니다. 만약 캡처를 생성시점으로 하기 위해서는 별도의Capture List
를 사용해야 합니다. 이 부분은 추후에 작성해보겠습니다.