iOS/Swift

클로저 (Closures) - 클로저 표현

Brad_Heo 2023. 4. 25. 22:54

클로저 (Closure)는 코드블럭으로 C와 Objective-C의 블럭(blocks)과 다른 언어의 람다(lambdas)와 비슷 합니다. 클로저는 어떤 상수나 변수의 참조를 캡쳐(capture)해 저장할 수 있습니다. Swift는 이 캡쳐와 관련된 모든 메모리를 알아서 처리 합니다.

캡처의 개념에 대해 익숙하지 않다고 걱정하지 않으셔도 됩니다. 값 캡쳐는 아래에서 자세히 설명해 두었습니다.

전역 함수(global functions)와 중첩 함수(nested function)은 실제 클로저의 특별한 경우 입니다. 클로저는 다음 세 가지 형태 중 하나를 갖습니다.

  • 전역 함수: 이름이 있고 어떤 값도 캡쳐하지 않는 클로저
  • 중첩 함수: 이름이 있고 관련된 함수로 부터 캡쳐할 수 있는 클로저
  • 클로저 표현: 경량화 된 문법으로 쓰여지고 관련된 문맥(context)으로부터 값을 캡쳐할 수 있는 이름이 없는 클로저

Swift에서 클로저 표현은 최적화 되어서 간결하고 명확합니다. 이 최적화에는 다음과 같은 내용을. 포함합니다.

  • 문맥(context)에서 인자 타입(parameter type)과 반환 타입(return type)의 추론
  • 단일 표현 클로저에서의 암시적 반환
  • 축약된 인자 이름
  • 후위 클로저 문법

용어가 생소해 어렵게 느끼실 수 있지만, 하나하나 뜯어보면 어렵지 않고 쉽습니다.

클로저 표현(Closure Expressions)

클로저 표현은 인라인 클로저를 명확하게 표현하는 방법으로 문법에 초점이 맞춰져 있습니다. 클로저 표현은 코드의 명확성과 의도를 잃지 않으면서도 문법을 축약해 사용할 수 있는 다양한 문법의 최적화 방법을 제공 합니다.

정렬 메소드 (The Sorted Method)

Swift의 표준 라이브러리에 sorted(by:)라는 알려진 타입의 배열 값을 정렬하는 메소드를 제공합니다. 여기 by에 어떤 방법으로 정렬을 수행할 것인지에 대해 기술한 클로저를 넣으면 그 방법대로 정렬된 배열을 얻을 수 있습니다. sorted(by:)메소드는 원본 배열은 변경하지 않습니다. 아래와 같이 이름으로 구성된 names배열을 sorted(by:) 메소드와 클로저를 이용해 정렬해 보겠습니다.

sorted(by:) 메소드는 배열의 콘텐츠와 같은 타입을 갖고 두개의 인자를 갖는 클로저를 인자로 사용 합니다. name의 콘텐츠는 String 타입이므로 (String, String) -> Bool의 타입의 클로저를 사용해야 합니다.

클로저를 제공하는 일반적인 방법은 함수를 하나 만드는 것 입니다. 위 타입을 만족 시키는 함수를 하나 만들면 정렬에 인자로 넣을 수 있는 클로저를 만들 수 있습니다.

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedBanes = names.sorted(by: backward)
// Ewa, Daniella, Chris, Barry, Alex

클로저 표현 문법 (Closure Expression Syntax)

클로저 표현 문법은 일반적으로 아래의 형태를 띱니다.

{ (parameters) -> return type in statements}
}

인자로 넣을 parmeters, 인자 값으로 처리할 내용을 기술하는 statements 그리고 return tyype 입니다. 앞의 backward클로저를 이용해 배열을 정렬하는 코드는 클로저 표현을 이용해 다음과 같이 바꿀 수 있습니다.

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool  in 
    return s1 > s2
})

이렇게 함수로 따로 정의된 형태가 아닌 인자로 들어가 있는 형태의 클로저를 인라인 클로저라 부릅니다.
클로저의 몸통(body)은 in 키워드 다음에 시작합니다. 사용할 인자 값과(parameters)반환 타입(return type)을 알았으니 이제 그것들을 적절히 처리해 넘겨 줄 수 있다는 뜻입니다. 클로저의 body가 짧으니 아래와 같이 한줄에 적을 수 있습니다.

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

문맥에서 타입 추론 (Inferring Type From Context)

위 예제에서 정렬 클로저는 String 배열에서 sorted(by:)메소드의 인자로 사용됩니다.
sorted(by:)의 메소드에서 이미 (String, String) -> Bool 타입의 인자가 들어와야 하는지 알기 떄문에 클로저에서 이 타입들은 생략 될 수 있습니다. 그래서 위 함수는 더 생략한 형태로 아래와 같이 기술 할 수 있습니다.

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

위와 같이 클로저의 인자 값과 반환 타입을 생략할 수 있지만, 가독성과 코드의 모호성을 피하기 위해 타입을 명시할 수도 있습니다.

단일 표현 클로저에서의 암시적 반환 (Implicit Returns from Single-Express Closures)

단일 표현 클로저에서는 반환 키워드를 생략할 수 있습니다.

reverseNames = names.sorted(by: { s1, s2 in s1 > s2 })

이렇게 표현해도 어떠한 모호성도 없습니다. s1과 s2를 인자로 받아 그 두값을 비교한 결과를 반환 합니다.

인자 이름 축약 (Shorthand Arguments Names)

Swift는 인라인 클로저에 자동으로 축약 인자 이름을 제공 합니다. 이 인자를 사용하면 인자 값을 순서대로 $0, $1, $2 등으로 사용할 수 있습니다. 축약 인자 이름을 사용하면 인자 값과 그 인자로 처리할 때 사용하는 인자가 같다는 것을 알기 때문에 인자를 입력 받는 부분과 in 키워드 부분을 생략할 수 있습니다. 그러면 이런 형태로 축약 가능 합니다.

reversedNames = names.sorted(by: { $0 > $1 })

연산자 메소드 (Operator Methods)

축약을 더 할 수 있습니다. String끼리 비교할 수 있는 비교 연산자(>) 구현.

reversedNames = names.sorted(by: >)