전 포스팅에서 마지막에 언급한 클로저는 지금 말하는 클로저와 조금 다른 의미의 객체이다. 실제로 스위프트에서 클로저(Closure)라 객체를 지칭하는 것은 대부분 지금부터 이야기할 클로저를 의미한다.
스위프트에서 클로저는 '일회용 함수'를 작성할 수 있는 구문이다. 일회용 함수란 한 번만 사용할 구문들의 집합이며, 그 형식은 함수로 작성되어 있어야한다는 제약조건이 있을 때 사용하는 함수로 굳이 함수의 이름을 작성할 필요가 없다는 특징을 가져 익명(Anonymous) 함수라고 부르기도 한다.
스위프트에서 제공하는 클로저는 이전 포스팅에서 학습했던 클로저와 그렇게 다른 개념은 아니다. 앞에서 학습했던 개념을 모두 포함한다고 생각하면 된다. 따라서 클로저는 자신이 정의되었던 문맥(Context)으로부터, 모든 상수와 변수의 값을 캡처하거나 레퍼런스를 저장하는 익명함수라고 할 수 있다.
스위프트에서 클로저라고 부르는 객체는 대부분 다음 세 가지 경우 중 하나에 해당한다.
- 전역함수 : 이름이 있으며, 주변환경에서 캡처할 어떤 값도 없는 클로저
- 중첩함수 : 이름이 있으며 자신을 둘러싼 함수로부터 값을 캡처할 수 있는 클로저
- 클로저 표현식 : 이름이 없으며 주변 환경으로부터 값을 캡처할 수 있는 경량 문법으로 작성된 클로저
전역함수, 중첩함수는 이전 포스팅에서 알아봤듯, 이번 포스팅에선 클로저 표현식에대해 다루고자 한다.
클로저 표현식
클로저 표현식은 함수와 달리 생략되는 부분이 많다. 우선 func키워드, 함수의 이름을 생략한다.
{ (매개변수) -> 반환타입 in
실행 구문
}
일반적인 함수의 정의라면 반환타입이 표현된 후 중괄호 { }가 와야하지만 시작 부분에서 이미 중괄호가 선언되었기 때문에 in 키워드를 사용해 실행 블록의 시작을 표현한다.
일반함수와 다른 점이 한 가지 더 있는데 반환값이 없을 때는 일반 함수처럼 반환값 타입을 생략하는 것이 아니라, 함수 타입을 표현하는 것처럼 빈 괄호를 사용해 반환값이 없음을 명시적으로 표현해야한다. 이는 클로저 표현식의 모호성?을 제거하기 위한 규칙이다.
{ () -> () in
print("클로저가 실행됩니다")
}
// Void도 사용 가능
{ () -> Void in
print("클로저가 실행됩니다")
}
작성된 클로저 표현식은 그 자체로 함수라고 할 수 있다. 클로저 표현식은 대부분 인자값으로 함수를 넘겨주어야 할 때 사용하지만, 직접 실행해볼 수도 있다. 두 가지 방법이 있는데 첫 번째 상수나 변수에 클로저 표현식을 할당하여 실행하는 방법을 살펴보자.
let f = { () -> Void in
print("클로저가 실행됩니다")
}
f()
/* 실행결과
클로저가 실행됩니다
다음 두 번째 방법은 은 클로저를 직접 실행하는 방법이다.
({ () -> Void in
print("클로저가 실행됩니다")
})()
/* 실행결과
클로저가 실행됩니다
상수 f까지 생략한 구문으로 클로저 표현식 전체를 소괄호로 감싸고, 여기에 함수 호출 연산자 '( )'를 붙이면 클로저 표현식이 실행된다. 클로저 표현식 전체를 소괄호로 감싸지 않을 경우 컴파일러에서 클로저 표현식이 아니라 실행값을 변수나 상수에 할당하려는 의도로 해석해 오류가 발생하므로 주의하자.
이번엔 매개변수가 있는 형태의 클로저 표현식을 알아보자.
let c = { (s1: Int, s2: String) -> Void in
print("s1: \(s1), s2:\(s2)")
}
c(1, "closure")
// 상수생략
({ (s1: Int, s2: String) -> Void in
print("s1: \(s1), s2:\(s2)")
})(1, "closure")
/* 실행결과
s1: 1, s2: "closure"
클로저 표현식과 경량문법
클로저 표현식은 주로 인자값으로 사용되는 객체이다. 그러므로 간결성을 극대화하기 위해 생략할 수 있는 구문들로 이루어져있다. 배열의 정렬 메소드 예제를 통해 실제로 클로저 표현식에 적용되는 경량문법에 대해 알아보자.
var value = [1, 9, 5, 7, 3, 2]
func order(s1: Int, s2: Int) -> Bool {
if s1 > s2 {
return true
} else {
return false
}
}
value.sort(by: order)
/* 실행결과
[9, 7, 5, 3, 2, 1]
정렬함수인 sort(by:)를 이용해 큰 순서대로 정렬하게 한 예제이다. true일 시에는 인자값의 위치가 그대로이고 false일 때는 인자값의 위치를 서로 변경한다. 위의 order 함수를 클로저 표현식으로 바꾸어 작성해보자.
{ (s1: Int, s2: Int) -> Bool in
if s1 > s2 {
return true
} else {
return false
}
}
이 클로저 표현식은 다음과 같이 sort메소드의 인자값으로 바로 사용이 가능하다.
value.sort(by: {
(s1: Int, s2: Int) -> Bool in
if s1 > s2 {
return true
} else {
return false
}
})
/* 실행결과
[9, 7, 5, 3, 2, 1]
이 클로저 표현식은 여러 형태로 간결화할 수 있다. 먼저 구문 자체를 요약할 수 있다.
{ (s1: Int, s2: Int) -> Bool in
return s1 > s2
}
이번엔 스위프트에서 제공하는 문법을 통해 클로저 표현식 자체를 요약해보자. 먼저 클로저 표현식은 반환값의 타입을 생략할 수 있다.
{ (s1: Int, s2: Int) in
return s1 > s2
}
' -> Bool'이 생략된 형태이다. 이와 같이 생략이되면 컴파일러는 return s1 > s2 부분에서 반환값타입이 Bool이라는 것을 추론하게 된다.
클로저 표현식에서 생략할 수 있는 또 하나가 바로 매개변수의 타입 정의 부분이다.
{ s1, s2 in return s1 > s2 }
매개변수의 타입 어노테이션이 생략되면서 매개변수를 감싸고 있던 괄호도 함께 생략되었다. 이 표현식을 sort 메소드의 인자값으로 넣어보자.
value.sort(by: { s1, s2 in return s1 > s2 })
처음에 대입했던 때와 비교해 많이 간결해진 것을 볼 수 있다. 하지만 여기서 더 생략이 가능하다. 바로 매개변수를 생략하는 것이다. 매개변수가 생략되면 매개변수명 대신 $0, $1, $2와 같은 이름으로 할당된 내부 상수를 이용할 수 있다. 이 값은 입력받은 인자값의 순서대로 매칭된다. 첫 번째 인자값이 $0에, 두 번째 인자값이 $1에 할당되는 방식이다. 즉, s1은 $0, s2는 $1이 사용되는 것이다.
매개변수가 생략되면 남는 것은 실행 구문이다. 때문에 in키워드로 기존과 같이 실행 구문과 클로저 선언 부분을 분리할 필요가 없어지므로 in키워드 역시 생략이 가능하다.
{ return $0 > $1 }
이 경우 어차피 컴파일러가 Bool 타입을 반환할 것을 알고있다. 그렇기 때문에 마지막으로 return 구문까지 생략이 가능하다.
value.sort(by: { $0 > $1 })
이게 최종적으로 간결화된 모습이다. 이전과 비교해 많이 간결화 된 것을 확인할 수 있다.
하지만 sort 메소드에서는 클로저 표현식보다 더 간결하게 표현할 수 있는 방법이있다. 이를 연산자 함수(Operator Functions)라고 부르는데, 연산자만을 사용하여 의미하는 바를 정확히 나타낼 수 있을 때 사용된다. 이를 이용해 최종적으로 sort 메소드를 정리해보면 다음과 같다.
value.sort(by: > )
출처 : 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 함수(Fuction) Ⅹ. @escaping, @autoescape (0) | 2022.05.26 |
---|---|
스위프트(Swift) - 함수(Function) Ⅸ. 트레일링 클로저(Trailing Closure) (0) | 2022.05.25 |
스위프트(Swift) - 함수(Function) Ⅶ. 함수의 중첩 (0) | 2022.05.23 |
스위프트(Swift) - 함수(Function) Ⅵ. 일급 함수의 특성 (0) | 2022.05.19 |
스위프트(Swift) - 함수(Function) Ⅴ. 변수의 생존 범위와 생명 주기 (0) | 2022.05.18 |