스위프트에서는 함수를 일급 객체로 취급한다. 따라서 함수를 다른 함수의 전달 인자로 사용할 수 있다.
매개변수로 함수를 갖는 함수를 고차 함수라고 부르는데,
그중 map, flatMap, compactMap에 대해 알아볼 예정이다.
map
맵은 자신을 호출할 때 매개변수로 전달된 함수를 실행하여 그 결과를 다시 반환해주는 함수이다.
스위프트에서 맵은 배열, 딕셔너리, 세트, 옵셔널 등에서 사용할 수 있다.
조금 더 정확히 말하자면 Sequence, Collection 프로토콜을 따르는 타입과 옵셔널은 모두 맵을 사용할 수 있다.
맵을 사용 하면 컨테이너가 담고 있던 각각의 값을 매개변수를 통해 받은 함수에 적용한 후 새 컨테이너에 포장하여 반환한다.
기존 컨테이너의 값은 변경되지 않고 새로운 컨테이너가 생성되어 반환되는 것이다.
그래서 맵은 기존 데이터를 변형하는 데 많이 사용한다.
사용 방법
예제를 통해 어떻게 사용하는지 알아보자
다음은 map 메서드를 사용해 배열 요소 각각에 2를 곱하는 예제이다.
let array = [1, 2, 3, 4, 5]
let newArray = array.map { element -> Int in
return element * 2
}
print(array) // [1, 2, 3, 4, 5]
print(newArray) // [2, 4, 6, 8, 10]
기존 배열의 값은 변경되지 않고, 새로운 배열이 생성되어 반환되는 것을 알 수 있다.
map vs. for-in
map 메서드의 사용법은 for-in 구문을 사용하는 것과 별반 차이가 없다. 다만, 코드의 재사용 측면이나 컴파일러 최적화 측면에서 본다면 성능 차이가 있다.
또, 새로운 컨테이너를 반환하기 때문에 다중 스레드 환경일 때 대상 컨테이너의 값이 스레드에서 변경되는 시점에 다른 스레드에서도 동시에 값이 변경되려고 할 때 예측치 못한 결과가 발생하는 부작용을 방지할 수 있다.
다음은 for-in 구문과 map 메서드를 사용한 예제이다.
let numbers = [1, 2, 3, 4, 5]
var doubledNumbers: [Int] = []
// for 구문 사용
for number in numbers {
doubledNumbers.append(number * 2)
}
// map 메서드 사용
doubledNumbers = numbers.map { $0 * 2 }
코드를 보면 map 메서드를 사용했을 때 for-in 구문을 사용한 것보다 간결하고 편리하게 각 요소의 연산을 실행하는 것을 볼 수 있다.
심지어 map 메서드를 사용하면 for-in 구문을 사용하기 위해 빈 배열을 처음 생성해 주는 작업도 필요 없다. 배열의 append 연산을 실행하기 위한 시간도 필요 없다.
map 메서드의 형식이 이전 예제와 차이가 있다면, 클로저 표현식을 사용하여 표현을 좀 더 간략화한 것이다.
flatMap, compactMap
플랫 맵은 맵과 달리 콘텍스트 내부의 콘텍스트를 모두 같은 위상으로 평평(flat)하게 펼쳐준다는 차이가 있다.
즉, 포장된 값 내부의 포장된 값의 포장을 풀어서 같은 위상으로 펼쳐준다는 뜻이다.
콤팩트 맵의 사용 방법 또한 플랫 맵과 같다.
Swift 4.1부터는 flatMap에서 compactMap으로 변경되지만, flatMap이 없어지는 것은 아니다.
다음은 사용 예제이다.
let arrA = ["aa", nil, "bb", "cc", nil, "dd"]
let arrB = arrA.flatMap { $0 }
let arrC = arrA.compactMap { $0 }
print(arrB) // ["aa", "bb", "cc", "dd"]
print(arrC) // ["aa", "bb", "cc", "dd"]
둘 다 동일한 결과를 나타낸다. 단, 1차원 배열을 사용했을 때만
기존 flatMap의 경우, 배열을 1. flatten 하게 만들어 주며, 2. nil 제거, 3. 옵셔널 바인딩 해주는 역할이었다.
1차원 배열에서 flatMap을 사용할 경우 이런 경고창이 뜰 것이다.
'flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value
Use 'compactMap(_:)' instead
즉, Swift 4.1부터 1차원 배열에서 nil을 제거하고 옵셔널 바인딩을 하고자 할 때 compactMap을 사용하면 된다.
2차원 배열의 경우를 살펴보자
let arrD: [[Int?]] = [[1, nil, 2], [3], [nil, 4]]
let flatMapEx = arrD.flatMap { $0 }
let compactMapEx = arrD.compactMap { $0 }
print(flatMapEx) // [Optional(1), nil, Optional(2), Optional(3), nil, Optional(4)]
print(compactMapEx) // [[Optional(1), nil, Optional(2)], [Optional(3)], [nil, Optional(4)]]
flatMap과 compactMap은 nil을 제거하지 않고 1차원 배열일 때만 nil을 제거한다.
flatMap의 경우 2차원 배열을 1차원 배열로 flatten 하게 만드는 반면, compactMap은 1차원 배열로 만들지 않는다.
결과적으로, 2차원 배열을 1차원 배열로 flatten 하게 만들 때는 flatMap을 사용하면 된다.
위 예제에서 flatMapEx는 1차원 배열이므로, compactMap으로 체이닝 연결 시 아래와 같이 콘텐츠만 뽑아낼 수 있다.
let arrD: [[Int?]] = [[1, nil, 2], [3], [nil, 4]]
let flatMapEx = arrD.flatMap { $0 }.compactMap { $0 }
print(flatMapEx) // [1, 2, 3, 4]
참고
[Swift] 고차함수(2) - map, flatMap, compactMap - jinShine
map map은 배열 내부의 값을 하나씩 mapping한다고 생각하면 쉽게 다가올껍니다. 각 요소에 대한 값을 변경하고자 할때 사용하고, 그 결과들을 배열의 상태로 반환합니다. Declaration 1func map (_ transform:
jinshine.github.io
'Study > Swift' 카테고리의 다른 글
[Swift] 고차함수(2) - filter, reduce (0) | 2022.09.26 |
---|---|
[Swift] RunLoop.Mode (0) | 2022.08.01 |
[Swift] Run Loop (0) | 2022.07.25 |
[Swift] Initializer 심화 (2) (0) | 2022.07.20 |
[Swift] Initializer 심화 (1) (0) | 2022.07.19 |