-
RangeSet (feat. Set, IndexSet)Swift 2024. 12. 26. 14:35
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 Swift 6에서 새로 도입된 RangeSet이라는것에 대해 학습해보겠습니다 🙋🏻
그럼 바로 가볼까요?
RangeSet
RangeSet은 범위로 표현되는 비교 가능한 모든 유형의 값의 집합입니다.
struct RangeSet<Bound> where Bound : Comparable
Swift 6에서 탑재되어 iOS 18 이상부터 해당 타입을 사용하여 코드를 구현할 수 있어요.
기본적으로 설명해보자면, RangeSet은 불연속적인 범위에 걸쳐 있는 비교가 가능한 값 집합을 효율적으로 표현할 수 있습니다.
RangeSet은 일반적으로 컬렉션의 인덱스 유형 범위를 저장해 컬렉션의 여러 하위 범위를 나타내는데 사용됩니다.
예시를 볼께요.
var numbers = [10, 12, -5, 14, -3, -9, 15] let negativeSubranges = numbers.subranges(where: { $0 < 0 }) // numbers[negativeSubranges].count == 3 numbers.moveSubranges(negativeSubranges, to: 0) // numbers == [-5, -3, -9, 10, 12, 14, 15]
이렇게 예시에서 불연속적이더라도 조건에 따라 비교가 가능한 값 집합을 표현할 수 있죠.
사실 이렇게만 알고 있으면 큰 의미가 없고, 우리가 잘 알고 있는 Set하고 어떤 차이가 있는지 비교해보면서 왜 나왔는지 알아보면 좋을것 같아요.
먼저 아래 코드를 볼까요?
// 기존 Range의 한계 var numbers = Array(1...15) let range = 0..<5 // 연속된 범위만 표현 가능 // RangeSet의 필요성 // 비연속적인 위치들을 효율적으로 다루기 위함 let indicesOfEvens = numbers.indices(where: { $0.isMultiple(of: 2) })
이처럼 기존 Range는 연속된 범위로만 표현이 가능했다면 RangeSet은 조건을 주어 비연속적인 위치에 있는 값들을 효율적으로 다룰 수 있어요.
// 이전: 개별 인덱스들을 배열로 관리 var selectedIndices: [Int] = [1, 3, 5, 7] // RangeSet: 범위 기반으로 효율적 관리 var selectedRanges = RangeSet<Int>() selectedRanges.insert(contentsOf: 1...3) selectedRanges.insert(contentsOf: 5...7)
또한 기존에는 개별 인덱스들을 배열로 관리해야 했고 추가했다면, RangeSet을 이용하여 범위 기반으로 효율적인 관리가 가능해집니다.
즉, 비연속적인 요소들을 쉽게 처리할 수 있죠.
이는 메모리 관점에서도 효율성을 높여줍니다.
// 이전: 모든 인덱스를 저장 var indices = IndexSet([1, 2, 3, 4, 5, 10, 11, 12]) // 8개 저장 // RangeSet: 범위로 압축 저장 var rangeSet = RangeSet() rangeSet.insert(contentsOf: 1...5) // 2개 저장 (시작과 끝) rangeSet.insert(contentsOf: 10...12) // 2개 저장 (시작과 끝)
이전에는 위처럼 8개 모두를 저장해야 했다면, RangeSet을 이용하면 시작과 끝의 데이터만을 저장하기에 메모리적인 효율성을 높여줍니다.
let numbers = Array(1...15) let indicesOfEvens = numbers.indices(where: { $0.isMultiple(of: 2) }) let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)
또한, 이렇게 컬렉션 연산의 확장을 자유롭고 편리하게 해줄 수 있습니다.
rangeSet.insert(contentsOf: 1...5) rangeSet.remove(contentsOf: 3...4)
그리고 더 이전보다 직관적으로 범위 연산에 대해 제공해주게 됩니다.
즉, 정리해보자면 Set은 개별 요소들을 각각 저장했다면 RangeSet은 범위를 가지고 저장하는겁니다.
근데 여기서 범위는 비연속적이여도 충분히 괜찮다는 소리입니다.
또한, 메모리의 효율성 측면에서도 Set은 모든 요소가 각각 메모리를 차지하지만 RangeSet은 범위의 시작과 끝만 저장하기에 효율성이 증대되죠.
Set을 사용하냐, RangeSet을 사용하냐는 목적에 따라 다를텐데요.
아래와 같은 경우 Set의 사용을 하는 경우라고 생각합니다.
1️⃣ 고유한 개별 요소들의 컬렉션이 필요한 경우
2️⃣ 요소들 간 순서가 중요치 않는 경우
3️⃣ 중복 허용하지 않는 경우
반면 RangeSet은 이런 경우에 사용될것 같습니다.
1️⃣ 연속된 정수 범위를 다루는 경우
2️⃣ 범위 기반 연산이 필요한 경우
3️⃣ 텍스트 선택이나 시간 범위와 같은 연속적인 범위를 다루는 경우
반복을 처리하는것에서도 차이가 있습니다.
// Set let set: Set = [1, 2, 3, 4, 5] for number in set { print(number) // 순서가 보장되지 않음 } // RangeSet let rangeSet = RangeSet(1...5) for range in rangeSet.ranges { print(range) // 범위 단위로 반복 } for number in rangeSet { print(number) // 순서대로 반복 }
Set을 사용하여 반복을 처리하면 그냥은 순서가 보장되지 않습니다.
반면 RangeSet으로 연속된 정수 범위를 다뤄본다면 이처럼 범위 단위로도 반복할 수 있고 순서대로도 반복할 수 있어요.
즉, 연속된 범위를 다루는데 최적화된 타입이 아닐까 싶습니다.
근데 여기서 더 하나 ☝️
IndexSet이라는것도 기존에 있죠.
어떤 차이가 있을까요?
타입 제한
우선 IndexSet은 Int 타입만 지원해줍니다.
// IndexSet: Int 타입만 지원 let indexSet = IndexSet([1, 2, 3]) // RangeSet: 제네릭하게 Comparable 타입 지원 let rangeSet = RangeSet<Int>(1...3) let stringIndexRangeSet = RangeSet<String.Index>(...)
반면 RangeSet은 제네릭하게 Comparable한 타입을 지원해주기에 다양한 타입에 더 용이합니다.
플랫폼 지원 측면
IndexSet은 Foundation 프레임워크의 일부라면 RangeSet은 Swift 표준 라이브러리의 일부로 모든 Swift 플랫폼에서 사용이 가능해요.
그렇기에 Foundation에 대한 의존성이 없는것도 장점입니다.
앞으로 Swift 언어로 서버도 많이 지원하고 있고 모든 생태계를 아우르고 있는 측면을 본다면 애플 플랫폼 전용인 IndexSet보다는 순수 Swift 측면에서의 RangeSet의 사용이 더 부각이 될 수 있다고 느껴집니다.
마무리
아마 Swift가 이제는 애플 생태계를 넘어 다양한 부분에서 활용될 수 있기에 이런 발전 사항에 같이 Swift 자체적으로도 발전하고 있는거 아닐까라는 생각이 많이 들었습니다 😃
레퍼런스
'Swift' 카테고리의 다른 글
Swift로 효율적인 디버그 로깅 시스템 구축하기 (10) 2025.01.06 NSObject에 대하여 (31) 2024.12.19 Explore the Swift on Server ecosystem (feat. WWDC 2024) (34) 2024.12.02 ETag 캐싱으로 앱 성능 최적화하기 (32) 2024.11.26 Consume noncopyable types in Swift (feat. WWDC 2024) (26) 2024.11.18