명명된 함수 생성 없이 실행되는 코드 그룹이다.
클로저는 코드에서 주변에 전달과 사용할 수 있는 자체가 포함된 기능 블럭이다.
Swift의 클로저는 다른 프로그래밍 언어에서 클로저, 익명 함수, 람다, 그리고 블럭과 유사하다.
클로저는 정의된 컨텍스트에서 모든 상수와 변수에 대한 참조를 캡처하고 저장할 수 있다.
이러한 상수와 변수를 폐쇄 (Closing Over)라고 한다.
Swift는 캡처의 모든 메모리 관리를 처리한다.
함수 (Functions)에서 소개한 전역과 중첩 함수는 클로저의 특별한 케이스이다.
클로저는 3가지 형태 중 하나를 취한다.
Swift의 클로저 표현식은 일반 시나리오에서 간단하고 깔끔한 구문을 장려하는 최적화를 통해 깔끔하고 명확한 스타일을 가지고 있다. 이러한 최적화에는 다음이 포함된다.
중첩 함수 (Nested Functions) 에서 소개된 중첩 함수는 더 큰 함수에 부분으로 자체 포함된 코드 블럭의 이름을 지정하고 정의하기 편리한 수단이다. 그러나 완전한 선언과 이름 없이 함수와 유사한 구조의 짧은 버전을 작성하는 것이 때때로 유용하다. 함수를 하나 이상의 인수로 사용하는 함수 또는 메서드로 작업할 때 특히 그렇다.
클로저 표현식 (Closure expressions) 은 간단하고 집중적인 구문으로 인라인 클로저로 작성하는 방법이다. 클로저 표현식은 명확성이나 의도를 잃지 않고 짧은 형태로 클로저를 작성 하기위한 몇가지 구문 최적화를 제공한다. 아래의 클로저 표현식 예제는 여러 반복에 걸쳐 sorted(by:) 메서드의 단일 예제를 구체화하는 최적화를 나타낸다. 각 예제는 동일한 기능을 보다 간결한 방식으로 표현한다.
Swift의 표준 라이브러리는 사용자가 제공하는 정렬 클로저의 출력을 기반으로 알려진 타입의 값 배열을 정렬하는 sorted(by:) 라는 메서드를 제공한다. 정렬 프로세스가 완료되면 sorted(by:) 메서드는 원본 배열과 같은 타입과 같은 크기의 올바르게 정렬된 요소의 새로운 배열로 반환한다. 기존 배열은 sorted(by:) 메서드로 수정되지 않는다.
아래 예제의 클로저 표현식은 알파벳 역순으로 String 값의 배열을 정렬하기 위해 sorted(by:) 메서드를 사용한다. 다음은 정렬하기 위한 초기화 배열이다:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by:) 메서드는 배열 내용과 동일한 타입의 두 인수를 사용하는 클로저를 허용하고 값이 정렬된 후 첫번째 값이 두번째 값의 앞 또는 뒤에 표시되어야 하는지 여부를 나타내는 Bool 값을 반환한다. 정렬 클로저는 첫번째 값이 두번째 값 앞에 나타나야 하는 경우 true 를 반환하고 그렇지 않으면 false 를 반환해야 한다.
이 예제는 String 값의 배열을 정렬하고 정렬 클로저는 (String, String) -> Bool 타입의 함수를 필요로 한다.
정렬 클로저를 제공하는 한가지 방법은 올바른 타입의 일반 함수를 작성하고 sorted(by:) 메서드에 인수로 전달하는 것이다:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
첫번째 문자열 (s1)이 두번째 문자열 (s2)보다 크다면 backward(::) 함수는 true 를 반환하고 이것은 정렬된 배열에 s1 은 s2 전에 나타난다는 의미이다. 문자열의 문자가 "더 크다"는 "알파벳 순으로 더 뒤에 나타난다"는 의미이다. 이것은 문자 B 는 문자 A 보다 "더 크다"이고 문자열 "Tom" 은 문자열 "Tim" 보다 더 크다. 알파벳 역순으로 정렬될 때 "Barry" 는 "Alex" 보다 앞에 위치한다.
그러나 이것은 본질적으로 단일 표현식 함수 (a > b)를 작성하는 다소 긴 방식이다. 이 예제에서는 클로저 표현식 구문을 사용하여 정렬 클로저를 인라인으로 작성하는 것이 좋다.
클로저 표현구는 아래와 같은 일반적인 형태를 가지고 있다:
{ (<#parameters#>) -> <#return type#> in
<#statements#>
}
클로저 표현구의 파라미터는 in-out 파라미터일 수 있지만 기본값을 가질 수 없다. 가변 파라미터의 이름을 지정하면 가변 파라미터를 사용할 수 있다. 튜플은 파라미터 타입과 반환 타입으로 사용될 수 있다.
아래 예제는 위에서 backward(::) 함수의 클로저 표현 버전이다:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
이 인라인 클로저를 위한 파라미터와 반환 타입의 선언은 backward(::) 함수에서 선언한 것과 동일하다. 두 경우 모두 (s1: String, s2: String) -> Bool 로 작성한다. 그러나 인라인 클로저 표현식을 위한 파라미터와 반환 타입은 중괄호 바깥이 아닌 안에 작성한다.
클로저의 본문의 시작은 in 키워드로 시작합니다. 이 키워드는 클로저의 파라미터와 리턴 타입 정의가 끝남을 나타내며 클로저의 본문이 시작함을 나타낸다.
클로저의 본문이 너무 짧기 때문에 한줄로 작성할 수 있다:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
이것은 sorted(by:) 메서드에 대한 전체 호출이 동일하게 유지되었음을 보여준다. 소괄호는 여전히 메서드의 전체 인수를 둘러싸고 있다. 그러나 인수는 이제 인라인 클로저이다.
정렬 클로저는 메서드에 인수로 전달되기 때문에 Swift는 파라미터 타입과 반환되는 값의 타입을 유추할 수 있다. sorted(by:) 메서드는 문자열 배열에서 호출되므로 인수는 (String, String) -> Bool 타입의 함수이어야 한다. 이는 (String, String) 과 Bool 타입을 클로저 표현식 정의에 일부러 작성할 필요가 없음을 의미한다. 모든 타입은 유추할 수 있기 때문에 반환 화살표 (->)와 파라미터의 이름을 둘러싼 소괄호를 생략할 수 있다:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
함수나 메서드에 클로저를 인라인 클로저 표현식으로 전달할 때 항상 파라미터 타입과 반환 타입을 유추할 수 있다. 결과적으로 클로저가 함수 또는 메서드 인수로 사용될 때 완전한 형태로 인라인 클로저를 작성할 필요가 없다.
그럼에도 불구하고 원하는 경우 타입을 명시적으로 만들 수 있으며 코드를 읽는 자가 모호성을 피할 수 있다면 그렇게 하는 것이 좋다. sorted(by:) 메서드의 경우 정렬이 발생한다는 사실에서 클로저의 목적이 명확하며 문자열 배열의 정렬을 지원하기 때문에 코드를 읽는 사람이 클로저가 String 값으로 작동할 가능성이 있다고 가정하는 것이 안전하다.
단일 표현 클로저 (Single-expression closures)는 이전 예제에서 return 키워드를 생략하여 단일 표현식으로 암시적으로 값을 반환할 수 있다:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
여기서 sorted(by:) 메서드의 인수의 함수 타입은 클로저에서 Bool 값이 반환되어야 하기 때문에 명확하다. 클로저의 본문에 Bool 값을 반환하는 단일 표현식 (s1 > s2)가 포함되므로 모호하지 않고 return 키워드를 생략할 수 있다.
Swift는 인라인 클로저에 $0, $1, $2 등 클로저의 인수값으로 참조하는데 사용할 수 있는 자동적으로 짧은 인수 이름 (shorthand argument names)을 제공한다.
클로저 표현식에 이런 짧은 인수 이름을 사용한다면 선언에 클로저의 인수 리스트를 생략할 수 있고 짧은 인수 이름의 수와 타입은 함수 타입에서 유추된다. 클로저 표현식이 본문으로 전체가 구성되기 때문에 in 키워드를 생략할 수도 있다:
reversedNames = names.sorted(by: { $0 > $1 } )
여기서 $0 와 $1 은 클로저의 첫번째와 두번째 String 인수를 참조한다. $1 이 짧은 인수에서 가장 높은 숫자이므로 클로저는 2개의 인수가 있다고 이해한다. 여기서 sorted(by:) 함수는 인수가 모두 문자열인 클로저로 기대하므로 짧은 인수 $0 과 $1 은 모두 타입 String이다.
실제로 위의 클로저 표현식을 더 짧게 작성하는 방법이 있다.
Swift의 String 타입은 보다 큰 연산자 (>)의 문자열 별 구현을 String 타입의 파라미터 2개가 있는 메서드로 정의하고 Bool 타입의 값을 반환한다.
이것은 sorted(by:) 메서드에 필요한 메서드 타입과 정확하게 일치한다.
따라서 간단하게 보다 큰 연산자를 전달할 수 있고 Swift는 문자열 특정 구현을 사용하기 원한다고 유추한다:
reversedNames = names.sorted(by: >)