🔥 후행 클로저
클로저 표현식을 함수의 마지막 인자로 전달해야 하고 클로저 표현식이 길다면, 후행 클로저(trailing closure)
문법을 사용하는 것이 유용할 수 있습니다. 후행 클로저는 함수 호출 괄호 뒤에 작성되지만, 여전히 함수의 인자로 취급됩니다. 후행 클로저 문법을 사용할 때는 함수 호출의 일부로 첫 번째 클로저의 인자 레이블을 작성하지 않습니다. 함수 호출은 여러 개의 후행 클로저를 포함할 수 있지만, 아래의 첫 번째 예제들은 단일 후행 클로저만 사용했습니다.
func someFunctionThatTakesAClosure(closure: () -> Void) { // 함수 본문이 여기에 들어갑니다 } // 후행 클로저를 사용하지 않고 이 함수를 호출하는 방법입니다: someFunctionThatTakesAClosure(closure: { // 클로저 본문이 여기에 들어갑니다 }) // 대신 후행 클로저를 사용하여 이 함수를 호출하는 방법입니다: someFunctionThatTakesAClosure() { // 후행 클로저 본문이 여기에 들어갑니다 }
swift
위의 클로저 표현식 섹션에서의 문자열 정렬 클로저는 sorted(by:)
메서드의 괄호 밖에 후행 클로저로 작성될 수 있습니다:
reversedNames = names.sorted() { $0 > $1 }
swift
클로저 표현식이 함수나 메서드의 유일한 인자로 제공되고 해당 표현식을 후행 클로저로 제공한다면, 함수를 호출할 때 함수나 메서드의 이름 뒤에 괄호 ()
를 작성할 필요가 없습니다.
reversedNames = names.sorted { $0 > $1 }
swift
후행 클로저는 클로저가 충분히 길어서 한 줄에 인라인으로 작성하는 것이 불가능할 때 가장 유용합니다. 예를 들어, Swift의 Array
타입은 클로저 표현식을 단일 인자로 받는 map(_:)
메서드를 가지고 있습니다. 클로저는 배열의 각 항목에 대해 한 번씩 호출되고, 해당 항목에 대해 매핑된 대체 값(아마도 다른 타입의)을 반환합니다. map(_:)
에 전달하는 클로저에 코드를 작성하여 매핑의 특성과 반환되는 값의 타입을 지정합니다.
제공된 클로저를 각 배열 요소에 적용한 후, map(_:)
메서드는 원래 배열의 해당 값과 동일한 순서로 새로운 매핑된 모든 값을 포함하는 새 배열을 반환합니다.
다음은 후행 클로저와 함께 map(_:)
메서드를 사용하여 Int
값의 배열을 String
값의 배열로 변환하는 방법입니다:
let digitNames = [ 0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine" ] let numbers = [16, 58, 510]
swift
위의 코드는 정수 숫자와 숫자의 영어 이름 버전 사이의 매핑 딕셔너리를 생성합니다. 또한 문자열로 변환될 준비가 된 정수 배열을 정의합니다.
이제 numbers
배열을 사용하여 후행 클로저로 배열의 map(_:)
메서드에 클로저 표현식을 전달하여 String
값의 배열을 생성할 수 있습니다.
let strings = numbers.map { (number) -> String in // 숫자를 number 변수에 할당 var number = number var output = "" repeat { // 숫자의 일의 자리를 구하여 digitNames에서 해당하는 문자열 조회 output = digitNames[number % 10]! + output // 숫자를 10으로 나누어 다음 자리수 처리 number /= 10 } while number > 0 // 완성된 문자열 반환 return output } // strings는 [String] 타입으로 유추됨 // 값은 ["OneSix", "FiveEight", "FiveOneZero"]
swift
map(_:)
메서드는 배열의 각 항목에 대해 클로저 표현식을 한 번씩 호출합니다. 매핑될 배열의 값에서 클로저의 입력 매개변수 number
의 타입을 유추할 수 있기 때문에 타입을 지정할 필요가 없습니다.
이 예제에서는 값이 클로저 본문 내에서 수정될 수 있도록 number
매개변수의 값으로 변수 number
가 초기화됩니다. (함수와 클로저에 대한 매개변수는 항상 상수입니다.) 클로저 표현식은 또한 매핑된 출력 배열에 저장될 타입을 나타내기 위해 String
의 반환 타입도 지정합니다.
클로저 표현식은 호출될 때마다 output
이라는 문자열을 만듭니다. 나머지 연산자(number % 10
)를 사용하여 number
의 마지막 자릿수를 계산하고, 이 숫자를 사용하여 digitNames
딕셔너리에서 적절한 문자열을 조회합니다. 클로저는 0보다 큰 모든 정수의 문자열 표현을 만드는 데 사용될 수 있습니다.
digitNames
딕셔너리에서 검색된 문자열은 output
의 앞
에 추가되어 효과적으로 숫자의 문자열 버전을 거꾸로 만듭니다. (number % 10
표현식은 16
에 대해 6
, 58
에 대해 8
, 510
에 대해 0
을 제공합니다.)
그런 다음 number
변수를 10
으로 나눕니다. 정수이므로 나누는 동안 내림되어 16
은 1
이 되고, 58
은 5
가 되며, 510
은 51
이 됩니다.
number
가 0
과 같아질 때까지 프로세스가 반복되며, 이때 output
문자열이 클로저에 의해 반환되고 map(_:)
메서드에 의해 출력 배열에 추가됩니다.
위의 예제에서 후행 클로저 문법을 사용하면 클로저가 지원하는 함수 바로 뒤에 클로저의 기능을 깔끔하게 캡슐화할 수 있습니다. map(_:)
메서드의 바깥 괄호로 전체 클로저를 감쌀 필요가 없습니다.
함수가 여러 개의 클로저를 사용한다면, 첫 번째 후행 클로저의 인자 레이블은 생략하고 나머지 후행 클로저에는 레이블을 지정해야 합니다. 예를 들어, 아래 함수는 사진 갤러리의 사진을 로드합니다:
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) { if let picture = download("photo.jpg", from: server) { completion(picture) } else { onFailure() } }
swift
사진을 로드하기 위해 이 함수를 호출할 때, 두 개의 클로저를 제공합니다. 첫 번째 클로저는 다운로드가 성공한 후 사진을 표시하는 완료 핸들러입니다. 두 번째 클로저는 사용자에게 오류를 표시하는 오류 핸들러입니다.
loadPicture(from: someServer) { picture in someView.currentPicture = picture } onFailure: { print("다음 사진을 다운로드할 수 없습니다.") }
swift
이 예제에서 loadPicture(from:completion:onFailure:)
함수는 네트워크 작업을 백그라운드로 보내고, 네트워크 작업이 완료되면 두 개의 완료 핸들러 중 하나를 호출합니다. 이런 방식으로 함수를 작성하면 성공적인 다운로드 후 사용자 인터페이스를 업데이트하는 코드와 네트워크 실패를 처리하는 코드를 깔끔하게 분리할 수 있습니다. 두 상황을 모두 처리하는 단일 클로저를 사용하는 대신 말이죠.
후행 클로저를 활용하면 코드의 가독성과 명확성을 높일 수 있습니다. 클로저 표현식이 길어질 때 특히 유용합니다. 함수나 메서드의 다른 인자와 시각적으로 구분되어 코드의 의도를 더 잘 전달할 수 있습니다.