스위프트에서 함수는 중첩하여 사용이 가능하다. 함수 내에 다른 함수를 작성해 사용할 수 있다는 뜻이다. 이렇게 작성된 함수를 중첩함수(Nested Function)라고 하고 중첩함수 중 함수의 내부에 작성된 함수를 내부함수(Inner Fuction), 내부 함수를 포함하는 바깥 함수는 외부함수(Outer Function)으로 구분한다.
함수를 중첩해서 정의하면 내부함수는 외부함수가 실행되는 순간 생성되고, 종료되는 순간 소멸하게된다. 외부함수는 프로그램이 실행될 때 생성되고 프로그램이 종료될 때 소멸하지만, 내부함수는 외부함수의 실행과 종료 사이에서 생겼다가 사라진다. 즉, 외부함수가 종료되면 내부함수도 더는 존재하지 않는 것이다. 이것이 내부 함수의 생명주기(Life Cycle)이다.
내부함수는 일반적으로 외부 함수를 거치지 않으면 접근할 수 없는데 이를 함수의 은닉성이라고 한다. 중첩된 함수를 구현하면 함수의 은닉성을 높일 수 있게 되는 것이다. 이제 예제를 살펴보자.
// 외부함수
func outer(base: Int) -> String {
// 내부함수
func inner(inc: Int) -> String {
return "\(inc)를 반환합니다"
}
let result = inner(inc: base + 1)
return result
}
outer(base: 3)
// "4를 반환합니다"
내부함수를 참조할 수 있는 곳은 그 함수를 선언해준 외부함수 이외에 없다. 나머지 외부 범위로부터 내부함수가 은닉되기 때문이다. 따라서 이러한 경우 내부 함수의 생명주기는 전적으로 외부함수에 의존하게된다. 외부함수가 실행되면서 내부함수에 대한 참조가 발생하면 생성되고, 외부함수가 종료되면서 내부함수에 대한 참조도 종료하면 내부함수는 소멸한다.
중첨함수는 앞서 설명한 일급함수의 특성과 맞물려 다양한 효과를 기대할 수 있다. 단순히 중첩함수를 작성해 외부함수에서 내부함수를 호출하는 용도로 사용하는게 아니라 다음과 같은 형식으로 내부 함수를 반환값으로 제공할 수도있다.
// 외부함수
func outer(param: Int) -> (Int) -> String {
// 내부함수
func inner(inc: Int) -> String {
return "\(inc)를 리턴합니다"
}
return inner
}
let fn1 = outer(param: 3) // outer()가 실행되고, 그 결과로 inner가 대입됨
let fn2 = fn1(30) // inner(inc: 30)과 동일
함수 outer가 실행결과로 inner를 반환한다. 이 예제에서 눈여겨볼 점은 바로 은닉성이 있는 내부함수 inner를 외부함수의 실행결과로 반환함으로써 내부함수를 외부에서도 접근할 수 있는 길이 열렸다는점이다.
이제까지 내부에서 정의된 함수는 오로지 외부함수를 통해서만 접근할 수 있었다. 이로인해 은닉성이 제공되었다. 하지만 내부함수를 이렇게 반환하게되면 outer 함수의 실행결과는 내부함수 inner 그 자체가 되므로 얼마든지 상수 fn1을 사용해 inner를 호출할 수 있게된다.
여기서 잠깐 짚고 넘어가야될게 있는데 바로 inner함수의 생명주기에 대해서이다. 본래 inner 함수는 외부함수인 outer가 실행 종료되면 소멸되도록 설계되어있다. 따라서 원래대로라면 다음 구문이 실행되었을 때 inner는 소멸되어야한다.
let fn1 = outer(param: 3) // outer()가 실행되고, 그 결과로 inner가 대입됨
그런데 이번 예제에선 inner 함수가 소멸하지 않고 fn1에 할당된 채로 생명을 유지하다 (30)이라는 함수 호출 연산구문을 만나 실행되는 것을 확인할 수 있다. 즉, 외부함수에서 내부함수를 반환하게 되면 외부함수가 종료되더라도 내부함수의 생명이 유지되는 것이다.
그렇다면 만약 내부함수에 외부함수의 지역 상수, 또는 지역변수가 참조되면 어떻게될까? 예제를 살펴보자.
func basic(param: Int) -> (Int) -> Int {
let value = param + 20 -----
|
func append(add: Int) -> Int { | // 클로저 범위
return value + add |
} -----
return append
}
let result = basic(param: 10) // ①
result(10) // ②
// 40
주목해야할 것은 상수 value인데 일반적으로 함수 내에서 정의된 값들은 그 함수가 종료되기 직전까지만 존재하기 때문에, value 상수는 ①의 실행이 종료되기 직전까지만 존재해야한다. 즉, ①의 실행이 완료될 때 함께 제거되어야한다는 뜻이다.
따라서 ②구문이 실행되는 시점에선 value 상수가 더는 존재하지 않으며, append 함수의 내부 블록에선 결과적으로 존재하지 않는 상수를 참조하고 있는 모양이 된다. 오류가 발생할 것이라 예상할 수 있는 부분이다. 하지만 실제로 실행해 보면 예상과 달리 코드는 문제없이 작동되고, 40이라는 결과값을 내기도하는 것을 볼 수 있다.
이러한 현상은 바로 클로저(Closure)때문이다. 더 정확히는 'append 함수가 클로저를 갖기 때문'이다. 클로저를 설명하면 다음과 같다.
- 클로저는 두 가지로 이루어진 객체이다. 하나는 내부함수이며, 또 다른 하나는 내부함수가 만들어진 주변 환경이다.
- 클로저는 외부함수 내에서 내부함수를 반환하고, 내부함수가 외부함수의 지역 변수나 상수를 참조할 때 만들어진다.
조금 간단히 요약한다면 " 클로저란 내부함수와 내부함수에 영향을 미치는 주변환경(Context)을 모두 포함한 객체이다."라는 의미다.
주변환경이라는 것은 내부 함수에서 참조하는 모든 외부 변수나 상수의 값, 그,리고 내부함수에서 참조하는 다른 객체까지를 의미한다. 이를 문맥(Context)라고 한다. 즉 클로저란 내부 함수와 이 함수를 둘러싼 주변 객체들의 값을 함께 의미하는 것이라 할 수 있다. 위 예제에서 표시해둔 것이 클로저의 범위라고 할 수 있다.
하지만 정확한 클로저의 설명은 아니다. 왜냐면 클로저에서 저장하는 주변환경은 변수나 객체 자체가 아니라 '값'이기 때문이다. 클로저가 만들어지려면 함수가 위와 같이 정의되는 것만으론 충분하지 않고, 실제로 basic 함수가 호출되어야 한다. 즉, 다음 구문이 실행되어야 클로저가 만들어질 수 있다는 것이다.
let result = basic(param: 10) // ①
이 구문이 실행될 때 생성되는 클로저는 위 예제에서 표시한 범위와 같지만 포함하는 것은 어디까지나 주변환경의 객체 자체가아니라 값이다. 따라서 상수 result에 저장되는 클로저는 다음과 같은 형태로 생성된다.
func append(add: Int) -> Int {
return 30 + add
}
내부함수를 둘러싼 주변환경 객체가 값으로 바뀌어 저장되는 것을 볼 수 있다. 이러한 클로저의 특성 때문에 같은 정의를 갖는 함수가 서로 다른 환경을 저장하는 결과가 생겨난다. 예제를 살펴보자.
let result1 = basic(param: 10)
let result2 = basic(param: 5)
// result1에 할당된 클로저 정의
func append(add: Int) -> Int {
return 30 + add
}
// result2에 할당된 클로저 정의
func append(add: Int) -> Int {
return 25 + add
}
이처럼 외부함수에서 정의된 객체가 만약 내부함수에서도 참조되고 있고, 이 내부함수가 반환되어 참조가 유지되고 있는 상태라면 클로저에 의해 내부함수 주변의 지역변수나 상수도 함께 저장된다. 정확히는 지역변수의 값이 '저장'되는 것이라 할 수 있다. 이를 "값이 캡처(Capture)되었다."라고 표현한다.
값의 캡처는 문맥에 포함된 변수나 상수의 타입이 기본 자료형이나 구조체 자료형일 때 발생하는데, 이러한 캡처 기능은 클로저의 고유기능이다.
출처: 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 함수(Function) Ⅸ. 트레일링 클로저(Trailing Closure) (0) | 2022.05.25 |
---|---|
스위프트(Swift) - 함수(Function) Ⅷ. 클로저(Closure) (0) | 2022.05.24 |
스위프트(Swift) - 함수(Function) Ⅵ. 일급 함수의 특성 (0) | 2022.05.19 |
스위프트(Swift) - 함수(Function) Ⅴ. 변수의 생존 범위와 생명 주기 (0) | 2022.05.18 |
스위프트(Swift) - 함수(Function) Ⅳ. InOut 매개변수 (0) | 2022.05.17 |