본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.
클로저 Closures
클로저는 코드 안에서 전달되고 사용될 수 있는 함수의 독립적인 블록이다. 스위프트에서의 클로저는 C나 Objective-C의 블록과 비슷하고 다른 프로그래밍 언어의 람다와 비슷하다. 클로저는 정의되는 맥락으로부터 어떠한 변수나 상수로든지 캡쳐하고 참조를 저장할 수 있다.
클로저는 다음 세가지 중 하나의 형태를 띄고 있다:
- 전역함수: 이름이 있고 어떠한 값도 캡쳐하지 않는 함수
- 중첩함수: 이름이 있고 감싸고 있는 함수로부터 값을 캡쳐할 수 있는 함수
- 클로저 표현: 주변의 맥락으로부터 값을 캡쳐할 수 있는 간단한 문법으로 쓰인 이름이 없는 클로저
swift의 클로저 표현은 간단하고 깔끔한 문법을 장려하는 최적화와 함께 간단하고 명확한 스타일을 가지고 있다. 최적화는 다음의 것들을 포함한다:
- 매개변수를 추론하고 맥락으로부터 값 타입을 반환
- 단일 표현 클로저로 부터 암시적 반환값
- 간단한 인자 이름
- 클로저 문법 추적
클로저 표현 Closure Expressions
중첩함수는 네이밍하는 데에 있어서 그리고 독립적인 코드 블록을 더 큰 함수의 부분으로서 정의하는 데에 있어서 편리한 수단이다. 하지만, 전체적인 선언과 이름 없이 함수와 같은 짧은 구로 사용하는게 더 유용할 때가 있다. 특히 하나 이상의 인자를 가지는 함수를 취하는 함수나 메서드를 사용할 때 그렇다.
클로저 표현은 직렬의 클로저를 간단하고 집중된 형태로 작성하는 방법이다. 클로저 표현은 명확성이나 의도의 손실을 피하면서 짧은 형태로 클로저를 작성하기 위해서 문법 최적화를 제공한다. 아래의 예시는 sorted(by:) 메서드의 반복을 간단한 방식으로 개선하는 모습을 통해 이러한 최적화를 설명해주고 있다.
The Sorted Method
Swift의 표준 라이브러리는 sorted(by:)라는 메서드를 제공한다. 이 메서드는 배열의 값을 정렬 클로저의 결과에 기반해서 정렬해준다. 한 번 정렬을 끝내면, sorted(by:) 메서드는 이전의 배열과 같은 타입과 크기를 가지고 요소들이 정렬된 새로운 배열을 반환한다. 기존의 원본 배열이 sorted(by:)에 의해 수정되는 것은 아니다.
아래의 클로저 표현 예시는 역순으로 String 값을 정렬하기 위해서 sorted(by:) 메서드를 사용한다. 다음은 정렬이 되어야하는 최초의 배열이다:
let names = ["Chris", "Alex", "Ewa, "Barry", "Daniella"]
sorted(by:) 메서드는 배열의 요소로서 타입이 같은 두개의 인자를 취하는 클로저를 받아들인다. 그리고 정렬이 되었을 때 첫번째 값이 두번째 값보다 먼저나와야하는지 나중에 나와야하는지를 말해주는 Bool 값을 반환한다. 이 경우에서 만약 첫번째 값이 두 번째 값보다 먼저 나와야한다면 정렬 클로저는 true를 반환해야한다.
이 예시는 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"]
만약 첫번째 String(s1)이 두번재 String(s2)보다 크다면, backward(_:_:) 함수는 s1이 s2보다 먼저 나와야한다는 것을 가리키기 위해서 true를 반환할 것이다. String 문자열에 대해서 "더 크다"라는 것은 "알파벳상으로 뒤에 위치한다"라는 것을 의미한다. 예를 들면 문자 "B"가 문자 "A"보다 "더 크다"라는 것을 의미하고, String "Tom"이 String "Tim"보다 "더 크다"라는 것을 의미하는 것이다.
하지만, 이것은 단일 표현 함수(a>b)를 표현하기엔 장황한 방법이라고 볼 수 있다. 이 경우엔 클로저 표현 문법을 사용해서 정렬 클로저를 직렬으로 만들어주는 것이 더 합리적일 것이다.
클로저 표현 문법 Closure Expression Syntax
클로저 표현 문법은 다음과 같은 일반적인 형태를 띈다:
{ (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 } )
맥락에서 타입 추론하기 Inferring Type From Context
정렬 클로저가 메서드에 인자로서 전달되기 때문에, swift는 그 매개변수와 반환 값의 타입을 추론할 수 있다. sorted(by:) 메서드는 배열에 호출이 되고 있다. 그래서 그 인자가 (String, String) -> Bool의 타입을 가진 함수여야한다. 이것은 (String, String)과 Bool 타입이 클로저 표현의 정의에서 쓰여지지 않아도 된다는 것을 의미한다. 대부분의 타입들이 추론될 수 있기 때문에, 반환 화살표 (->)와 괄호가 생략될 수 있다:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
클로저를 함수나 메서드에 직렬 클로저 표현으로 전달할 때 매개변수 타입과 반환 타입을 추론하는 것은 항상 가능하다. 그 결과로 클로저가 함수나 메서드 인자로 사용될 때 직렬 클로저를 완전한 형태로 쓸 필요가 없게 된다.
그럼에도 원한다면 타입을 명시해줄 수 있다. 그리고 그렇게 하면 코드를 읽는 사람 입장에서 애매함을 피할 수 있다. sorted(by:) 메서드의 경우에는, 정렬이 발생한다는 점에서 클로저의 목적이 확실하다. 그래서 코드를 읽는 사람이 String 값을 다룬다는 것을 추론하기 쉬워진다.
단일 표현 클로저로부터의 암시적 반환 Implicit Returns from Single-Expression Closures
단일 표현 클로저는 선언할 때 return 키워드를 생략함으로서 암시적으로 단일 표현으로부터의 결과를 반환할 수 있다.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
sorted(by:) 메서드의 인자의 함수타입은 클로저에 의해서 Bool이 반환되어야한다는 것을 확실히 한다. 클로저의 바디가 단일 표현(s1 > s2)을 포함하고 있기때문에, 모호함이 없고 return 키워드가 생략될 수 있다.
단축된 인자 이름 Shorthand Argument Names
swift는 직렬 클로저에 자동적으로 단축된 인자 이름을 제공한다. 그리고 이것은 $0, $1, $2 등의 이름으로 클로저의 클로저의 인자의 값을 추론하기 위해 사용된다.
만약에 클로저 표현 내에서 이러한 단축된 인자이름을 사용한다면, 정의할 때 클로저의 인자 리스트를 생략할 수 있다. 단축된 인자 이름의 타입은 예상되는 함수 타입으로부터 추론될 수 있고, highest numbered 단축된 인자이름은 클로저가 취하는 인자를 결정한다. 클로저 표현이 in 키워드 또한 생략될 수 있다:
reversedNames = names.sorted(by: { $0 > $1 } )
$0, $1은 클로저의 첫번째와 두번째 String인자를 의미한다. $1이 가장 높은 번호를 가진 단축된 인자 이름이기 때문에, 클로저는 두개의 인자를 취하는 것으로 이해될 수 있다. sorted(by:) 메서드가 인자가 두개인 클로저를 예상하기 때문에, 단축된 인자 $0, $1이 모두 String이다.
오퍼레이터 메서드 Operator Methods
더 짧게 줄일 방법이 있다. swift의 String 타입은 greater-than 오퍼레이터를 두개의 인자를 가진 메서드로 String-specific 구현을 정의한다. 이것은 sorted(by:) 메서드와 일치한다.
reversedNames = names.sorted(by: >)
참조
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
https://babbab2.tistory.com/81
'YAGOM CAREER STARTER' 카테고리의 다른 글
[TIL] 20230116: Refactoring with delegation (0) | 2023.01.18 |
---|---|
[TIL] 20230113: Protocol2 (0) | 2023.01.14 |
[TIL] 20230110: Protocol1 (0) | 2023.01.11 |
[TIL] 20230109: Singleton (0) | 2023.01.10 |
[토요스터디A반] 20230107: Property Observer, MVC, Notification Center (0) | 2023.01.07 |