연관 값을 무시하면서 연속된 Swift 열거형 케이스를 억제하려면 별도의 케이스 키를 비교하고 각 연속 구간에서 첫 번째 전체 값을 유지합니다. 이 로컬 Mac 예제는 순서를 보존하며, 다른 케이스 뒤에 같은 케이스가 다시 나타나는 것도 허용합니다.
중복으로 간주할 대상을 정확히 정의하기
데스크톱 그래픽 미리 보기가 대기 상태, 이름이 지정된 도형 그리기, 이름이 지정된 미리 보기 표시로 이루어진 합성 단계 시퀀스를 만든다고 가정해 보겠습니다. 케이스 전환만 간결하게 나열하려고 합니다. 도형 이름이 서로 달라도 연속된 두 그리기 단계는 중복으로 간주합니다.
Swift 열거형은 각 케이스에 서로 다른 연관 값을 담을 수 있습니다. 이 값들은 열거형 값의 일부이므로, 이를 무시하려면 비교 규칙을 명시해야 합니다. Swift 언어 가이드의 열거형에서 이 차이를 설명합니다.
이 작업에서는 다음 규칙을 사용합니다.
- 첫 번째 입력 값을 유지합니다.
- 뒤이어 나오는 값의 케이스가 앞선 연속 구간의 케이스와 같으면 억제합니다.
- 유지되는 각 요소의 연관 값을 온전히 보존합니다.
- 다른 케이스 뒤에 다시 나타나는 케이스는 유지합니다.
- 입력 순서를 보존하며, 입력이 비어 있으면 결과도 비어 있습니다.
예시 입력에 적용할 동작은 다음과 같습니다. 모든 이름은 예시용입니다.
| 입력 값 | 결정 | 이유 |
|---|---|---|
.idle |
유지 | 첫 번째 값 |
.drawing("Circle") |
유지 | 케이스가 바뀜 |
.drawing("Square") |
억제 | 계속 그리는 중 |
.ready("Preview A") |
유지 | 케이스가 바뀜 |
.ready("Preview B") |
억제 | 계속 준비 상태 |
.drawing("Triangle") |
유지 | 준비 상태 뒤에 그리기 케이스가 옴 |
.idle |
유지 | 케이스가 바뀜 |
.idle |
억제 | 계속 대기 상태 |
따라서 유지되는 그리기 값은 \"Circle\"과 \"Triangle\"입니다. \"Square\" 페이로드는 의도적으로 제외됩니다. 데스크톱 뷰에 업데이트된 모든 도형 이름을 표시해야 한다면 이 규칙은 뷰에 필요한 변경 사항을 버립니다. 그런 소비자에게는 페이로드를 반영하는 비교를 선택하세요.
Mac에서 로컬 Swift 예제를 실행하기
작동하는 Swift 개발 환경을 갖춘 Mac을 사용하세요. Swift.org는 Apple 플랫폼 개발에 Xcode를 권장하며, Swift Package Manager를 사용한 명령줄 실행 파일 작업 과정도 안내합니다. 도구를 준비해야 한다면 Swift 설치 — macOS를 따르세요.
Terminal에서 새 작업 디렉터리에 작은 실행 파일 패키지를 만듭니다.
mkdir EnumCaseDemo
cd EnumCaseDemo
swift package init --name EnumCaseDemo --type executable
생성된 실행 파일 소스의 내용을 다음 완전한 프로그램으로 바꿉니다. 이 프로그램은 Swift 표준 라이브러리 타입을 사용하므로 import나 패키지 의존성이 필요하지 않습니다. 입력과 출력은 메모리에 유지되며, 프로그램은 유지된 예시 단계를 출력합니다.
enum PreviewStep: Equatable {
case idle
case drawing(String)
case ready(String)
enum Kind: Equatable {
case idle
case drawing
case ready
}
var kind: Kind {
switch self {
case .idle:
return .idle
case .drawing(_):
return .drawing
case .ready(_):
return .ready
}
}
var label: String {
switch self {
case .idle:
return "idle"
case .drawing(let shape):
return "drawing: \(shape)"
case .ready(let name):
return "ready: \(name)"
}
}
}
func suppressConsecutiveCases(
_ steps: [PreviewStep]
) -> [PreviewStep] {
var result: [PreviewStep] = []
var previousKind: PreviewStep.Kind? = nil
for step in steps {
let currentKind = step.kind
if let previous = previousKind,
previous == currentKind {
continue
}
result.append(step)
previousKind = currentKind
}
return result
}
let input: [PreviewStep] = [
.idle,
.drawing("Circle"),
.drawing("Square"),
.ready("Preview A"),
.ready("Preview B"),
.drawing("Triangle"),
.idle,
.idle
]
let expected: [PreviewStep] = [
.idle,
.drawing("Circle"),
.ready("Preview A"),
.drawing("Triangle"),
.idle
]
let output = suppressConsecutiveCases(input)
precondition(output == expected)
precondition(suppressConsecutiveCases([]).isEmpty)
precondition(
suppressConsecutiveCases([
.drawing("Circle"), .drawing("Square")
]) == [.drawing("Circle")]
)
precondition(
suppressConsecutiveCases([
.idle, .drawing("Circle"), .idle
]) == [.idle, .drawing("Circle"), .idle]
)
for step in output {
print(step.label)
}
해당 디렉터리에서 패키지를 실행합니다.
swift run EnumCaseDemo
빌드 메시지 다음에 표시될 예상 프로그램 출력은 다음과 같습니다.
idle
drawing: Circle
ready: Preview A
drawing: Triangle
idle
이 예제들은 가져온 문서를 바탕으로 검토했으며, 조사 환경에 Swift 실행 파일이 없어 여기서 실행하지는 않았습니다. 포함된 사전 조건은 예상 유지 페이로드, 빈 입력, 연속되지만 페이로드가 다른 값, 전환 뒤 다시 나타나는 케이스를 확인합니다.
별도의 케이스 키가 작동하는 이유
kind 속성은 모든 전체 열거형 값을 페이로드가 없는 작은 키로 변환합니다. .drawing("Circle")와 .drawing("Square")는 모두 .drawing 키를 생성합니다. 필터링 함수는 이 키를 비교하면서 원래 step을 추가하므로 유지된 연관 값은 그대로 보존됩니다.
이 함수는 키 하나만 기억하면 됩니다. 처음에는 previousKind가 nil이므로 첫 번째 요소가 통과합니다. 요소를 유지한 뒤에는 해당 요소의 종류를 기억합니다. 억제된 요소는 종류가 같으므로 그 요소의 키로 기억값을 갱신해도 달라지는 것은 없습니다.
이 구현은 입력을 한 번 순회합니다. 이 고정된 열거형과 크기가 일정한 키의 경우, 첫 요소 이후 각 요소마다 케이스 키를 한 번 비교하며 반환 배열 외에 일정한 보조 상태만 사용합니다. Swift의 Sequence 문서에서는 순차 반복과 일반적인 선형 순회 모델을 설명합니다.
전체 PreviewStep도 Equatable을 준수하므로 사전 조건에서 유지된 문자열을 포함해 완전한 결과를 확인할 수 있습니다. 연관 값이 Equatable을 준수하는 적합한 열거형은 Swift가 합성한 동등성 비교를 지원합니다. 자세한 내용은 프로토콜을 참조하세요. Kind에서 케이스 비교를 처리하면 프로그램 전체의 동등성 정의를 바꾸지 않고도 이 필터링 작업만의 규칙을 명시할 수 있습니다.
kind 안의 모든 케이스를 다루는 switch도 유용한 유지 관리 점검 지점입니다. 새 케이스를 추가할 때는 해당 키와 매핑도 추가하세요. 새 케이스를 기존 케이스와 뜻하지 않게 묶는 포괄 매핑은 피하세요. Swift의 완전한 열거형 전환은 열거형에서 설명합니다.
Combine에서도 같은 비교 사용하기
Mac 코드에서 이미 Combine 퍼블리셔를 사용한다면 Apple은 사용자 지정 비교를 위한 removeDuplicates(by:)을 제공합니다. 두 번째 값을 중복으로 간주해야 할 때 이 조건자는 true을 반환합니다. Apple은 원하는 비교가 출력 타입의 일반적인 동등성 비교와 다를 때 이 연산자를 사용할 수 있다고 명시합니다. macOS 10.15 이상에서 사용할 수 있습니다. removeDuplicates(by:)를 참조하세요.
다음 코드는 독립 실행 프로그램이 아니라 확장 코드 조각입니다. Combine을 사용할 수 있는 Mac에서 위의 전체 예제 뒤에 추가하세요. 기존 PreviewStep 정의와 input 배열을 재사용합니다.
import Combine
let subscription = input.publisher
.removeDuplicates { previous, current in
previous.kind == current.kind
}
.sink { step in
print("Combine: \(step.label)")
}
이 코드는 유지된 다섯 단계를 \"Combine: \" 접두사와 함께 출력합니다. 조건자는 케이스 키를 비교하고, 퍼블리셔는 원래의 유지된 열거형 값을 전달합니다. Apple의 연산자 예제도 한 속성을 비교하고 다른 속성은 무시하는 같은 원리를 따릅니다. removeDuplicates(by:)
필요한 구독 기간 동안 반환된 구독을 유지하세요. Apple 문서에 따르면 sink(receiveValue:)가 반환한 값을 해제하면 구독이 해제됩니다. 이 오버로드는 Apple의 시퀀스 퍼블리셔 예제처럼 실패 타입이 Never인 퍼블리셔에 적용됩니다. sink(receiveValue:)
코드를 적용하기 전에 경계 조건 확인하기
이미 유한한 로컬 목록이 있다면 배열 함수를 사용하세요. 값이 퍼블리셔를 통해 들어온다면 Combine 연산자를 사용하세요. 여기서 두 방식은 같은 케이스 전환 규칙을 나타냅니다.
| 요구 사항 | 적절한 비교 또는 접근 방식 |
|---|---|
| 각 케이스 구간의 첫 번째 전체 값 유지 | 예시처럼 kind 비교 |
| 연관 문자열의 변경 사항 유지 | 일반 동등성으로 전체 값 비교 |
| 각 구간의 마지막 전체 값 유지 | 보류 중인 값을 저장하고 케이스 전환 시와 마지막에 반영 |
| 다른 케이스 뒤에 다시 나타나는 케이스 유지 | 전체 케이스 목록을 관리하지 말고 연속 구간 비교 |
| 서로 다른 배열을 각각 필터링 | 함수를 별도로 호출합니다. 호출마다 비교 상태는 비어 있는 상태에서 시작합니다. |
마지막 페이로드를 유지하려면 이 프로그램은 첫 값을 즉시 추가하므로 알고리즘을 달리해야 합니다. 마찬가지로 하나의 논리적 시퀀스를 여러 배열로 나누면 비교 상태가 초기화됩니다. 한 배치의 끝에 있는 그리기 값과 다음 배치의 시작에 있는 그리기 값은 별도 호출을 거치면 둘 다 유지됩니다. 여러 배치를 하나의 시퀀스처럼 처리해야 한다면 배치 경계 너머로 이전 키를 보존하세요.
Foundation은 Apple 플랫폼 앱에 컬렉션과 기타 기본 기능을 제공하지만, 이 계산은 전적으로 Swift 타입으로 구현됩니다. 퍼블리셔 방식에는 특히 Combine이 필요합니다. import Foundation을 추가해도 해당 연산자는 가져오지 않습니다. Apple은 이 프레임워크들을 Foundation과 removeDuplicates(by:)에서 각각 설명합니다.
동기식 배열 함수에도 동시성 처리는 필요하지 않습니다. 비교 상태는 각 함수 호출에 속합니다. 나중에 공유 데스크톱 표시 상태를 도입한다면 그 상태의 격리를 별도로 정의하세요. Apple 문서에서는 MainActor를 실행자가 메인 디스패치 큐와 동등한 싱글턴 액터로 설명합니다. 중복 억제 자체가 액터를 선택하지는 않습니다. MainActor
여기서 다루는 로컬 작업에서 결정적인 선택은 각 구간에서 어떤 페이로드를 남길지입니다. 첫 페이로드를 선택하면 별도의 케이스 키를 통해 동작을 쉽게 읽고 확인할 수 있습니다. 케이스 전환에서 원래 값을 유지하고, 연속된 키가 같으면 억제하며, 다시 나타나는 케이스는 통과시킵니다.
출처
- 열거형 — Swift.org, The Swift Programming Language.
- 프로토콜 — Swift.org, The Swift Programming Language.
- Swift 설치 — macOS — Swift.org.
- Sequence — Apple Developer Documentation.
- removeDuplicates(by:) — Apple Developer Documentation.
- sink(receiveValue:) — Apple Developer Documentation.
- Foundation — Apple Developer Documentation.
- MainActor — Apple Developer Documentation.