sorted(by:)
메서드의 단일 예제를 구체화하는 최적화를 나타냅니다. 각 예제는 동일한 기능을 보다 간결한 방식으로 표현합니다.sorted(by:)
라는 메서드를 제공합니다. 정렬 프로세스가 완료되면 sorted(by:)
메서드는 원본 배열과 같은 타입과 같은 크기의 올바르게 정렬된 요소의 새로운 배열로 반환합니다. 기존 배열은 sorted(by:)
메서드로 수정되지 않습니다.String
값의 배열을 정렬하기 위해 sorted(by:)
메서드를 사용합니다. 다음은 정렬하기 위한 초기화 배열입니다:sorted(by:)
메서드는 배열 내용과 동일한 타입의 두 인자를 사용하는 클로저를 허용하고 값이 정렬된 후 첫번째 값이 두번째 값의 앞 또는 뒤에 표시되어야 하는지 여부를 나타내는 Bool
값을 반환합니다. 정렬 클로저는 첫번째 값이 두번째 값 앞에 나타나야 하는 경우 true
를 반환하고 그렇지 않으면 false
를 반환해야 합니다.String
값의 배열을 정렬하고 정렬 클로저는 (String, String) -> Bool
타입의 함수를 필요로 합니다.sorted(by:)
메서드에 인자로 전달하는 것입니다:s1
)이 두번째 문자열 (s2
)보다 크다면 backward(_:_:)
함수는 true
를 반환하고 이것은 정렬된 배열에 s1
은 s2
전에 나타납니다. 문자열의 문자가 "더 크다"는 "알파벳 순으로 더 뒤에 나타난다"는 의미입니다. 이것은 문자 B
는 문자 A
보다 "더 크다"이고 문자열 "Tom"
은 문자열 "Tim"
보다 더 큽니다. 알파벳 역순으로 정렬될 때 "Barry"
는 "Alex"
보다 앞에 위치합니다.a > b
)를 작성하는 다소 긴 방식입니다. 이 예제에서는 클로저 표현식 구문을 사용하여 정렬 클로저를 인라인으로 작성하는 것이 좋습니다.backward(_:_:)
함수의 클로저 표현 버전입니다:backward(_:_:)
함수에서 선언한 것과 동일합니다. 두 경우 모두 (s1: String, s2: String) -> Bool
로 작성합니다. 그러나 인라인 클로저 표현식을 위한 파라미터와 반환 타입은 중괄호 바깥이 아닌 안에 작성합니다.in
키워드로 시작합니다. 이 키워드는 클로저의 파라미터와 리턴 타입 정의가 끝남을 나타내며 클로저의 바디가 시작함을 나타냅니다.sorted(by:)
메서드에 대한 전체 호출이 동일하게 유지되었음을 보여줍니다. 소괄호는 여전히 메서드의 전체 인자를 둘러싸고 있습니다. 그러나 인자는 이제 인라인 클로저입니다.sorted(by:)
메서드는 문자열 배열에서 호출되므로 인자는 (String, String) -> Bool
타입의 함수이어야 합니다. 이는 (String, String)
과 Bool
타입을 클로저 표현식 정의에 일부러 작성할 필요가 없음을 의미합니다. 모든 타입은 유추할 수 있기 때문에 반환 화살표 (->
)와 파라미터의 이름을 둘러싼 소괄호를 생략할 수 있습니다:sorted(by:)
메서드의 경우 정렬이 발생한다는 사실에서 클로저의 목적이 명확하며 문자열 배열의 정렬을 지원하기 때문에 코드를 읽는 사람이 클로저가 String
값으로 작동할 가능성이 있다고 가정하는 것이 안전합니다.return
키워드를 생략하여 단일 표현식으로 암시적으로 값을 반환할 수 있습니다:sorted(by:)
메서드의 인자의 함수 타입은 클로저에서 Bool
값이 반환되어야 하기 때문에 명확합니다. 클로저의 바디에 Bool
값을 반환하는 단일 표현식 (s1 > s2
)가 포함되므로 모호하지 않고 return
키워드를 생략할 수 있습니다.$0
, $1
, $2
등 클로저의 인자값으로 참조하는데 사용할 수 있는 자동적으로 짧은 인자 이름 (shorthand argument names)을 제공합니다.in
키워드를 생략할 수도 있습니다:$0
와 $1
은 클로저의 첫번째와 두번째 String
인자를 참조합니다. $1
이 짧은 인자에서 가장 높은 숫자이므로 클로저는 2개의 인자가 있다고 이해합니다. 여기서 sorted(by:)
함수는 인자가 모두 문자열인 클로저로 기대하므로 짧은 인자 $0
과 $1
은 모두 타입 String
입니다.String
타입은 보다 큰 연산자 (>
)의 문자열 별 구현을 String
타입의 파라미터 2개가 있는 메서드로 정의하고 Bool
타입의 값을 반환합니다. 이것은 sorted(by:)
메서드에 필요한 메서드 타입과 정확하게 일치합니다. 따라서 간단하게 보다 큰 연산자를 전달할 수 있고 Swift는 문자열 특정 구현을 사용하기 원한다고 유추합니다:()
를 작성하지 않아도 됩니다:Array
타입은 단일 인자로 클로저 표현식을 가지는 map(_:)
메서드가 있습니다. 이 클로저는 배열의 각 아이템에 대해 한번 호출되고 아이템에 대해 매핑된 대체값 (다른 타입일 수 있음)이 반환됩니다. map(_:)
에 전달한 클로저에 작성된 코드에 따라 매핑 특성과 반환된 값의 타입을 지정합니다.map(_:)
메서드는 기존 배열에 해당값과 같은 순서로 새로 매핑된 값의 새로운 배열을 반환합니다.Int
값의 배열을 String
값의 배열로 변환하기 위해 후행 클로저와 map(_:)
메서드를 어떻게 사용하는지 나타냅니다. 배열 [16, 58, 510]
은 새로운 배열 ["OneSix", "FiveEight", "FiveOneZero"]
을 생성하는데 사용됩니다:number
배열을 사용하여 후행 클로저로 map(_:)
메서드로 클로저 표현식을 전달하여 String
값의 배열을 생성할 수 있습니다:map(_:)
메서드는 배열에 각 아이템을 위해 클로저 표현식을 호출합니다. 매핑할 배열의 값에서 유추할 수 있으므로 클로저의 입력 파라미터 인 number
타입을 지정할 필요가 없습니다.number
는 클로저의 number
파라미터의 값으로 초기화되기 때문에 값은 클로저 바디내에서 수정될 수 있습니다 (함수와 클로저의 파라미터는 항상 상수입니다). 클로저 표현식은 출력 매핑된 출력 배열에 저장될 타입을 나타내기 위해 String
타입도 반환 타입으로 지정합니다.output
이라는 문자열을 만듭니다. 나머지 연산자 (number % 10
)를 이용하여 number
의 마지막 숫자를 계산하고 digitNames
딕셔너리에 적절한 숫자 문자열을 찾습니다. 클로저는 0보다 큰 정수에 대한 문자열 표현을 생성하는데 사용할 수 있습니다.NOTE 딕셔너리 서브 스크립트는 키가 존재하지 않는경우에 값을 찾는 것을 실패하기 위해 옵셔널 값을 반환합니다. 그래서digitNames
딕셔너리의 서브 스크립트를 호출할 때는 느낌표를 붙여 줍니다 (!
). 위의 예제에서digitNames
딕셔너리에number % 10
은 항상 유효한 서브 스크립트 키를 보장하므로 느낌표는 서브 스크립트의 옵셔널 반환 값에 저장된String
값을 강제로 언래핑 하는데 사용됩니다.
digitNames
딕셔너리에서 반환된 문자열이 output
앞에 추가되어 숫자의 문자열 버전을 역순으로 효과적으로 빌드합니다 (표현식 number % 10
은 16
의 경우 6
, 58
의 경우 8
, 510
의 경우 0
을 제공합니다).number
변수는 10
으로 나누어 집니다. 이것은 정수이기 때문에 나누면 버림이 되고 16
은 1
, 58
은 5
, 510
은 51
이 됩니다.number
가 0
이 될때까지 반복하고 그 때 output
문자열은 클로저로 부터 반환되고 map(_:)
메서드로 부터 출력 배열에 추가됩니다.map(_:)
메서드의 바깥 소괄호로 감쌀 필요가 없습니다.loadPicture(from:completion:onFailure:)
함수는 네트워크 작업을 백그라운드로 전달하고 네트워크 작업이 완료되면 두 완료 처리기 중 하나를 호출합니다. 이러한 방식으로 함수를 작성하면 두 상황을 모두 처리하는 하나의 클로저를 사용하는 대신 성공적인 다운로드 후 사용자 인터페이스를 업데이트 하는 코드에서 네트워크 오류를 처리하는 코드를 명확하게 분리할 수 있습니다.NOTE 완료 핸들러 (Completion handlers) 는 특히 여러 핸들러가 중첩되어 있으면 읽기 어려울 수 있습니다. 이것을 대체하기 위해선 동시성 (Concurrency) 에서 설명되어진 것과 같이 비동기 코드를 사용하면 됩니다.
incrementer
라는 중첩 함수가 포함된 makeIncrementer
라는 함수의 예입니다. 중첩된 incrementer()
함수는 둘러싸인 컨텍스트에 runningTotal
과 amount
인 2개의 값을 캡처합니다. 이 값을 캡처한 후에 incrementer
는 호출될 때마다 amount
로 runningTotal
을 증가시키는 클로저로 makeIncrementer
에 의해 반환됩니다.makeIncrementer
의 반환 타입은 () -> Int
입니다. 이것은 단순한 값이 아닌 함수 를 반환한다는 의미입니다. 반환하는 함수에는 파라미터가 없으며 호출될 때마다 Int
값을 반환합니다. 함수가 다른 함수를 반환하는 방법을 알아보려면 반환 타입으로의 함수 타입 (Function Types as Return Types) 을 참고 바랍니다.makeIncrementer(forIncrement:)
함수는 반환될 현재 증가분을 저장하기 위해 runningTotal
이라는 정수 변수를 정의합니다. 이 변수는 0
으로 초기화 됩니다.makeIncrementer(forIncrement:)
함수는 forIncrement
의 인자 라벨의 하나의 Int
파라미터를 가지고 amount
라는 파라미터 이름을 가지고 있습니다. 이 파라미터에 전달된 인자값은 반환된 증가 함수가 호출 될 때마다 runningTotal
을 얼마나 증가시켜야 하는지 지정합니다. makeIncrementer
함수는 실제 증가를 수행하는 incrementer
라는 중첩 함수를 정의합니다. 이 함수는 간단하게 amount
를 runningTotal
에 더하고 그 결과를 반환합니다.incrementer()
함수는 비정상적으로 보일 수 있습니다:incrementer()
함수는 파라미터가 없으며 함수 바디내에 runningTotal
과 amount
를 참조하고 있습니다. 둘러싸인 함수에 runningTotal
과 amount
대한 참조 (reference) 를 캡처하고 함수 내에서 사용합니다. 참조를 캡처하는 것은 makeIncrementer
호출이 종료될 때 runningTotal
과 amount
가 사라지지 않고 다음에 incrementer
함수가 호출될 때 runningTotal
을 사용할 수 있습니다.NOTE 최적화로 Swift는 값이 클로저에 의해 변경되지 않고 클로저가 생성된 후 값이 변경되지 않는 경우 값의 복사본을 캡처하고 저장할 수 있습니다.Swift는 더이상 필요하지 않을때 변수를 처리하는 것과 관련된 모든 메모리 관리도 처리합니다.
makeIncrementer
수행에 대한 예입니다:runningTotal
변수에 10
을 더하는 증가 함수를 참조하도록 incrementByTen
이라는 상수를 설정합니다. 함수를 여러번 호출하면 이 동작이 수행되는 동작을 보여줍니다:runningTotal
변수에 참조 저장됩니다:incrementByTen
)을 다시 호출하면 그것의 runningTotal
변수는 이어서 증가되고 incrementBySeven
으로 캡처된 변수는 영향을 주지 않습니다:NOTE 클래스 인스턴스의 프로퍼티에 클로저를 할당하고 클로저가 인스턴스 또는 멤버를 참조하여 해당 인스턴스를 캡처하면 클로저와 인스턴스 사이에 강한 참조 사이클이 생성됩니다. Swift는 캡처 목록을 사용하여 이러한 강한 참조 사이클을 깨뜨립니다. 자세한 내용은 클로저의 강한 참조 사이클 (Strong Reference Cycles for Closures) 을 참고 바랍니다.
incrementBySeven
과 incrementByTen
은 상수이지만 이러한 상수가 참조하는 클로저는 캡처한 runningTotal
변수를 계속 증가시킬 수 있습니다. 이는 함수와 클로저가 참조 타입 (reference types) 이기 때문입니다.incrementByTen
은 클로저 자체의 내용이 아니라 상수를 가리키는 클로저 선택입니다.alsoIncrementByTen
호출은 incrementByTen
호출과 같음을 보여줍니다. 2개 모두 같은 클로저를 참조하기 때문에 둘다 증가하고 같은 러닝 합계를 반환합니다.@escaping
을 작성할 수 있습니다.someFunctionWithEscapingClosure(_:)
함수는 인자로 클로저를 가지고 있고 함수 바깥에 선언된 배열에 추가합니다. 함수의 파라미터에 @escaping
을 표시하지 않으면 컴파일 시 에러가 발생합니다.self
를 참조하는 이스케이프 클로저은 self
가 클래스의 인스턴스를 참조하는 경우 특별한 고려가 필요합니다. 이스케이프 클로저에 self
캡처는 강한 참조 사이클이 생기기 쉽습니다. 참조 사이클에 대한 자세한 내용은 자동 참조 카운팅 (Automatic Reference Counting) 을 참조 바랍니다.self
를 캡처하려면 사용할 때 명시적으로 self
를 작성하거나 클로저의 캡처 목록에 self
를 포함합니다. self
를 명시적으로 작성하는데 의도를 표현하고 참조 사이클이 없음을 확인하도록 상기시켜 줍니다. 예를 들어 아래 코드에서 someFunctionWithEscapingClosure(_:)
에 전달된 클로저는 명시적으로 self
를 참조합니다. 반대로 someFunctionWithNonescapingClosure(_:)
에 전달된 클로저는 비이스케이프 클로저입니다. 즉 암시적으로 self
를 참조할 수 있습니다.self
를 캡처하고 암시적으로 self
를 참조하는 doSomething()
입니다:self
가 구조체 또는 열거형 인스턴스이면 항상 암시적으로 self
를 참조할 수 있습니다. 그러나 이스케이프 클로저는 구조 또는 열거 인스턴스이면 self
에 대한 변경 가능한 참조를 캡처할 수 없습니다. 구조와 열거는 구조체와 열거형은 값타입 (Structures and Enumerations Are Value Types) 에서 설명 했듯이 공유 변경을 허용하지 않습니다.someFunctionWithEscapingClosure
함수 호출은 변경 가능한 메서드 내부에 있기 때문에 에러이고 self
는 변경 가능합니다. 이것은 이스케이프 클로저는 구조체인 self
를 변경가능한 참조로 캡처할 수 없다는 규칙을 위반합니다.assert(condition:message:file:line:)
함수는 condition
과 message
파라미터에 대한 자동 클로저를 가집니다. condition
파라미터는 오직 디버그 빌드인지 판단하고 message
파라미터는 condition
이 false
인지만 판단됩니다.customersInLine
배열의 첫번째 요소는 삭제되지만 클로저가 실제로 호출되기 전까지 삭제되지 않습니다. 클로저가 호출되지 않으면 클러저 내부의 표현식은 판단되지 않습니다. 이것은 배열의 요소가 삭제되지 않는다는 의미입니다. customerProvider
타입은 String
이 아니고 파라미터가 없고 문자열을 반환하는 () -> String
입니다.serve(customer:)
함수는 소비자의 이름을 반환하는 명시적 클로저를 가집니다. 아래 serve(customer:)
의 버전은 같은 동작을 수행하지만 명시적 클로저를 가지는 대신에 파라미터 타입에 @autoclosure
속성을 표기하여 자동 클로저를 가집니다. 이제 클로저 대신 String
인수를 받는 것처럼 함수를 호출할 수 있습니다. customerProvider
파라미터의 타입은 @autoclosure
속성으로 표시되므로 인자는 자동으로 클로저로 변환됩니다.NOTE 자동 클로저 남용은 코드 이해를 어렵게 만들 수 있습니다. 컨텍스트와 함수 이름은 판단이 연기되고 있음을 분명히 해야합니다.
@autoclosure
와 @escaping
속성을 둘다 사용하면 됩니다. @escaping
속성은 위의 이스케이프 클로저 (Escaping Closures) 에서 설명되어 있습니다.customerProvider
인자로 전달된 클로저를 호출하는 대신에 collectCustomerProviders(_:)
함수는 클로저를 customerProviders
배열에 추가합니다. 이 배열은 함수의 범위 밖에 선언됩니다. 이것은 배열에 클로저는 함수가 반환된 후에 실행될 수 있다는 의미입니다. 그 결과 customerProvider
인자의 값은 함수의 범위를 벗어날 수 있어야 합니다.