클로저는 코드 내에서 사용하고 전달될 수 있는 블록 단위로 C나 Objective-C의 블록이나 다른 프로그래밍 언어의 람다와 유사하다.
클로저는 선언된 문맥에서 상수나 변수를 참조한 값을 캡처하거나 저장할 수 있다. 이를 변수나 상수에 대한 클로징(closing over those constants and variables
)이라고 한다. 스위프트는 이를 위한 메모리 관리를 지원하고 있다.
클로저는 세 가지 형태가 있다.
스위프트는 클로징을 통해 간결하고 최적화에 적합한 코드를 지원하고 있다. 컨텍스트 내에서 파라미터와 리턴 값을 추론하고, 싱글 익스프레션 클로저에서 리턴을 생략하거나, 아규먼트 이름을 단축어로 쓰거나, 트레일링 클로저 문법 등이 이 최적화 사항이다.
중첩 함수는 더 큰 함수의 일부를 코드 블록으로 표현하기 위한 방법이다. 하지만 이름이나 전체 선언 없이 함수와 유사한 구조로 짧게 코드를 작성하는 게 유요할 때가 있다. 특히 아규먼트의 일부로 사용할 함수 또는 메소드를 쓸 때 말이다.
클로저 표현(closure expressions)로 짧게 인라인 클로저를 사용할 수 있다. 더 간결한 방식으로 최적화에 적합한 클로저를 사용해보자.
sorted(by:)
메소드는 입력값으로 받은 데이터 배열을 정렬한다. 정렬을 마친 뒤 이 메소드는 입력값으로 받은 데이터와 같은 길이, 같은 타입의 정렬된 새로운 배열을 반환한다. 원본 데이터 자체가 정렬되지는 않았다는 데 주목하자.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by:)
메소드는 배열 원소로서 같은 타입의 아규먼트 두 개를 받는 클로저를 받아들인 뒤, 값이 정렬되고 나면 첫 번째 데이터 값이 두 번째 데이터 값 앞 또는 뒤에 나타나야 하는지를 알려주는 불리언 값을 리턴한다. 첫 번째 값이 두 번째 값 앞에 나타나야 하면 true
, 그렇지 않으면 false
를 리턴한다.
위 예시의 경우 문자열 배열을 정렬하기 때문에 클로저는 (String, String) -> Bool
타입이다.
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames: ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
sorted
메소드에서 사용한 backward
는 주어진 배열의 각 값을 정렬하는 기준이 된다. a
가 b
보다 작기 때문에 주어진 문자열은 알파벳 반대 순서대로 정렬된다. 하지만 이 경우 backward
함수를 외부에 길게 써야 하는데, 보다 짧게 쓸 수 있다.
클로저 표현을 쓸 때 이 문법을 따른다. {(parameters) -> return type in statements}
이때 파라미터는 인-아웃일 수는 있지만, 디폴트 값을 설정할 수는 없다. 가변 파라미터도 이름을 선언한 뒤에는 사용할 수 있다. 파라미터 타입이나 리턴 타입으로 튜플 역시 쓸 수 있다.
위의 정렬 메소드에 사용한 backward
함수를 이렇게 써보자.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
인라인 클로저 표현에서 파라미터와 리턴 타입을 ()
안에 적어야 한다는 데 주의하자.
클로저 안의 내용이 짧다면 더 짧게 쓸 수도 있다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
이 경우 아규먼트는 인라인 클로저다.
정렬 클로저가 아규먼트로서 메소드에 전달되는데, 이때 스위프트는 파라미터와 리턴할 값 타입을 추론할 수 있다. sorted(by:)
메소드가 문자열 배열을 받기 때문에 아규먼트는 (String, String) -> Bool
임을 알 수 있다. 추론 가능하기 때문에 클로저 표현을 작성할 때 쓰지 않아도 된다. 모든 타입이 추론 가능하다면 리턴 화살표나 괄호 또한 생략할 수 있다.
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
이처럼 클로저를 함수나 메소드에 인라인 클로저 표현으로 전달할 때 파라미터나 리턴 타입을 추론할 수 있다. 즉 클로저를 함수나 메소드 아큐먼트로 사용할 때에는 인라인 클로저를 짧게 작성할 수 있다.
가독성을 위해서라면 언제든지 타입을 명확하게 작성할 수도 있다. sorted(by:)
메소드를 쓸 때 클로저를 왜 사용하는지 밝힌다면 문자열 값을 받는다는 것을 보여줄 수 있기 때문이다.
한 줄 짜리 클로저(single-expression closures)를 쓸 때 리턴이라는 키워트를 생략할 수 있다.
reversedNames = names.sorted(by: {s1, s2 in s1 > s2})
s1 > s2
이라는 표현이 불리언 값을 리턴하기 때문에 리턴 타입을 생략할 수 있다.
클로저 표현 안에서 아큐먼트 이름을 축약해서 사용하고 싶다면 클로저의 아규먼트 리스트를 생략할 수 있다. 아규먼트 이름 축약어의 타입은 함수 타입을 예상해서 추론할 수 있다.
$
를 통해 클로저가 참조하는 아규먼트를 표시하자. 클로저 표현이 바디 안에서 모두 만들어지기 때문에 in
이라는 키워드도 생략할 수 있다.
reversedNames = names.sorted(by: { $0 > $1 } )
$0, $1
이라는 표현이 문자열 첫 번째, 두 번째 아규먼트임을 알 수 있고, 이 경우 클로저가 아규먼트 두 개를 사용하고, sorted(by:)
함수가 문자열을 모두 아규먼트로 가지는 클로저를 가지기 때문에 이 축약어 $0, $1
또한 문자열임을 추론할 수 있다.
훨씬 더 간략한 표현 방식이 존재한다. 스위프트의 문자열 타입이 지원하는 >
연산자를 통해 문자열을 비교할 수 있기 때문이다. 즉 문자열 파라미터 두 개를 입력받고 불 타입을 리턴할 수 있다. 정확히 앞에서 정렬 메소드에서 사용한 기능과 동이라다.
reversedNames = names.sorted(by: >)