함수 내의 함수 -> 중첩 함수 (Nested Function)
중첩 함수를 정의하면, 내부 함수는 외부 함수가 실행되는 순간 생성되고, 종료되는 순간 소멸.
외부 함수는 프로그램이 실행될 때 생성, 종료될 때 소멸.
일반적으로 함수는 자신을 참조하는 곳이 있으면 생성, 참조하는 곳이 사라지면 제거되는 생명 주기를 가짐.
func outer(base: Int) -> String {
func inner(_ int: Int) -> String {
return "\(int) 를 반환합니다."
}
let result = inner(base + 1)
return result
}
print(outer(base: 5)) // 6 를 반환합니다.
내부 함수 반환 예제
func outer() -> (Int) -> String {
func inner(_ base: Int) -> String {
return "\(base) 반환"
}
return inner(_:)
}
let fn1 = outer()
print(fn1(30)) // 30 반환
위와 같은 방식을 통해 외부에서 내부 함수에 접근 가능.
fn1 에 inner(_:)
가 할당되었기 때문에, inner 는 소멸하지 않는다.
반면 outer()
는 let fn1 = outer()
구문 이후 소멸된다. 더이상 자신을 참조하는 곳이 없기 때문.
func outer(base: Int) -> (Int) -> Int {
func inner(_ param:Int) -> Int {
return param + base
}
return inner
}
let result = outer(base: 30)
print(result(10)) // 40
내부 함수에서 외부 함수의 지역 변수 base
를 참조하는 예시.
inner(_:)
이 클로저를 갖기 때문에 base
를 참조해도 오류 없이 작동한다.
클로저
- 두 가지로 이루어진 객체. 하나는 내부 함수이며, 또 다른 하나는 내부 함수가 만들어진 주변 환경
- 클로저는 외부 함수 내에서 내부 함수를 반환하고, 내부 함수가 외부 함수의 지역 변수나 상수를 참조할 때 만들어짐.
=> 내부 함수, 내부 함수에 영향을 미치는 주변 환경(Context) 를 모두 포함한 객체.
outer 가 남아있는 것이 아니라, 값만 복사되어
func inner(_ param: Int) -> Int {
return param + 30
}
과 같은 형태로 저장된다. ( 값이 캡쳐되었다 )
위의 클로저는 함수형 언어에서 공통으로 가지는 소프트웨어 아키텍처적인 개념이고, 이 장의 클로저는 익명 함수를 지칭한다. 전혀 다른 것은 아니고, 이 익명 함수가 위의 내용을 포함.
클로저는 자신이 정의되었던 문맥으로부터, 모든 상수와 변수의 값을 캡처하거나 레퍼런스를 저장하는 익명 함수.
스위프트에서 클로저라고 부르는 객체는 세 가지 경우 중 하나.
1. 전역 함수: 이름이 있으며, 주변 환경에서 캡처할 값이 없는 클로저
2. 중첩 함수: 이름이 있으며 자신을 둘러싼 함수로부터 값을 캡처할 수 있는 클로저
3. 클로저 표현식: 이름이 없으며 주변 환경으로부터 값을 캡처할 수 있는 경량 문법으로 작성된 클로저
let f = { () -> () in
print("클로저 실행")
}
f() // 클로저 실행
중괄호 내에 타입을 선언한다. 반환값이 없을 때에도 빈 괄호로 표시해주어야 함.
할당 없이 바로 실행도 가능. 이때는 소괄호로 클로저를 감싸주어야 함.
({(_ base: Int) -> Void in
print("\(base) 입력됨.")
})(4) // 4 입력됨.
예시
var a = [13,4,5,3,1]
a.sort(by: {(s1: Int, s2: Int) -> Bool in return s1 > s2})
print(a)
클로저는 반환값, 입력값 타입을 생략할 수 있음. 컴파일러가 추론해줌.
var a = [13,4,5,3,1]
a.sort(by:{s1, s2 in return s1 < s2})
print(a) // 1 3 4 5 13
매개변수도 생략 가능. 미리 할당된 내부 상수를 이용해, 순서대로 호출가능.
var a = [13,4,5,3,1]
a.sort(by:{return $0 < $1}) // $0 부터 첫 번째 인자
print(a) // 1 3 4 5 13
클로저를 인자 값으로 전달하는 상황에서 문법을 변형할 수 있음.
마지막 인자 값이 클로저일 때, 이를 인자값 형식으로 작성하는 대신, 함수의 뒤에 꼬리처럼 붙일 수 있는 문법을 의미. 이때 레이블은 생략.
var a = [2,13,4,5,3,1]
a.sort(){(s1, s2) in return s1 > s2}
필요한 인자 값이 하나일때 아예 소괄호 생략도 가능
var a = [2,13,4,5,3,1]
a.sort{return $0 > $1}
> a
클로저를 함수나 메소드의 인자값으로 사용할 때에는 용도에 따라 @escaping
과 @autoclosure
속성을 부여할 수 있음.
인자값으로 전달된 클로저를 저장해 두었다가 나중에 다른 곳에서도 실행할 수 있도록 함.
인자값으로 전달된 클로저는 기본적으로 "탈출불가" 의 성격을 가짐. 해당 클로저를 1. 함수 내에서 2. 직접 실행을 위해서만 사용해야 하는 것을 의미.
아래는 예시.
func callback(fn: () -> Void) {
let f = fn // 오류
f()
}
callback(fn: {() -> Void in print(123)})
인자로 전달된 클로저를 변수나 상수에 대입하려면 @escaping 을 이용해 탈출 가능 인자로 만들어줌.
func callback(fn: @escaping () -> Void) {
let f = fn
f()
}
callback(fn: {print(123)}) // 123
인자값으로 전달된 일반 구문이나, 함수 등을 클로저로 래핑하는 역할. 일반 구문을 넣더라도 컴파일러가 알아서 클로저로 만들어 사용.
func condition(statement: () -> Bool ){
if statement() {
print("true")
} else {
print("false")
}
}
condition(statement: {5 > 3})
condition{5 > 3}
// 위 두 구문은 같은 의미. 클로저 경량화 + 트레일링 클로저
// @autoclosure 를 사용하면 다음과 같다.
func condition(statement: @autoclosure () -> Bool) {
if statement() {
print("true")
} else {
print("false")
}
}
condition(statement: (5 > 2)) // true
추가 예시
var arr : [String] = []
func f(_ function: @autoclosure () -> Void) {
arr = Array(repeating: "", count: 3) // 배열 크기 3으로 확장
function()
}
f(arr.insert("hello", at: 1)) // 평문을 넣어도 정상 작동한다.
print(arr) // ["", "hello", "", ""]