일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- swift opaque type
- uikit toast
- swift custom ui
- swift dashed line
- swift bottomsheet
- swift 점선
- task cancel
- swift navigationcontroller
- domain data
- task cancellation
- swift 백준
- BFS
- UIKit
- custombottomsheet
- coordinator
- scene delegate
- RxSwift
- custom navigation bar
- swift existential type
- reactorkit
- SWIFT
- DP
- traits
- swift concurrency
- button configuration
- identifiable
- 버튼 피드백
- swift associated type
- Tuist
- custom ui
- Today
- Total
김경록의 앱 개발 여정
[Swift] 타입 추상화(associated, Opaque, Existential, Generic) 본문
Swift는 타입 안정성이 아주 강력한 언어입니다.
특히 타입 추상화(type abstraction)와 관련된 기능들이 다양해서,
제대로 활용하면 코드의 유연성과 재사용성이 크게 높아지죠.
오늘은 그중에서도 뭔가 비슷한 것 같으면서도 분명히 다른 네 가지 기능을 정리해보려고 합니다:
- 연관 타입 (associatedtype)
- 제네릭 (Generic)
- 불명확 타입 (Opaque Type)
- 실존 타입 (Existential Type)
어디에 어떻게 써야 할지, 어떤 차이가 있는지 살펴보고 그래서 '이게 왜 존재해야하는가'에 대해 개인적인 생각을 담아봤습니다.
💡 한글 용어는 야곰님의 『스위프트 프로그래밍 4판』을 기준으로 사용했습니다.
1. 🧩 연관 타입 (associatedtype) — 프로토콜 내부에서의 타입 추상화
📌 개념
associatedtype은 프로토콜 내부에서 타입을 추상화하는 기능이에요.
즉, 프로토콜을 따르는 타입이 어떤 구체 타입을 쓸지는 채택한 쪽에서 나중에 정하게 됩니다.
protocol Container {
associatedtype Item
func append(_ item: Item)
func get(index: Int) -> Item
}
🎯 목적
- 프로토콜이 행동만 정의하고, 구체적인 타입은 구현체가 지정
- 다양한 타입에 대해 유연한 추상화 가능
- Swift의 Protocol-Oriented Programming 스타일에 잘 맞음
⚠️ 특징
- associatedtype이 있는 프로토콜은 그 자체로 타입으로 사용할 수 없습니다.
- 대신 제네릭이나 불명확 타입(some)과 함께 사용해야 해요.
let c: Container // ❌ 에러
2. 🧬 제네릭 (Generic) — 타입과 함수에서의 추상화
📌 개념
제네릭은 타입을 외부에서 주입받는 형태의 추상화입니다.
함수, 구조체, 클래스 등에서 사용할 수 있습니다. 이건 많이들 접하셨을거 같아요
struct Stack<T> {
var items = [T]()
mutating func push(_ item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
- 다양한 타입에 대해 재사용할 수 있는 유연한 코드
- 성능 최적화에 유리 (정적 디스패치)
- 타입 안정성 유지
3. 🔒 불명확 타입 (Opaque Type, some) — 타입을 감추되 고정
📌 개념
some 키워드는 내부 구현 타입은 숨기고, 외부엔 프로토콜만 노출하는 방식이에요.
SwiftUI에서 많이 보셨을 겁니다.
func makeView() -> some View {
Text("Hello")
}
- 이 함수는 Text를 반환하지만, 외부에서는 View라는 프로토콜만 보입니다.
🎯 목적
- associatedtype이 있는 프로토콜을 반환할 때 쓸 수 있는 방법
- 타입을 감추면서도 정적 디스패치를 유지 (성능 좋음)
- 복잡한 내부 구현을 숨기고 싶을 때 유용 (예: SwiftUI의 View 반환)
4. 🧳 실존 타입 (Existential Type, any) — 다형성을 위한 추상화
📌 개념
실존 타입이란 프로토콜을 타입처럼 사용하는 것입니다.
Swift 5.7부터 any Protocol 문법이 도입되었고, 6부터는 반드시 명시적으로 써야 합니다.
protocol Drawable { func draw() }
final class Circle: Drawable {
func draw() {
print("그림 그려용")
}
}
let shape: any Drawable = Circle() shape.draw() // ✅ 동적 디스패치
any Drawable은 여러 타입을 하나의 변수로 묶기 위한 런타임 컨테이너입니다.
❗ 비교: 실존 타입 없이 쓴 경우
let shape = Circle() shape.draw() // ✅ 정적 디스패치
- 이건 shape의 타입이 Circle로 정적으로 확정되기 때문에, 디스패치도 정적으로 이뤄집니다.
- 성능 최적화가 더 잘 됩니다.
🎯 목적
- 다형성을 극대화할 수 있음
- 여러 타입을 하나의 타입처럼 다루고 싶을 때
- 런타임에서 타입이 결정되는 유연한 코드 작성
⚠️ 특징
- 동적 디스패치를 사용 → 성능 손해 가능
- 타입 인라인 최적화가 어려움
- Swift 6부터는 any 키워드를 반드시 써야 함
- 실존 타입은 변수 활용에 유연성을 줄 수 있지만, 성능의 아쉬움이 있을 수 있으니 사용시 고려를 해봐야합니다
🧭 네 가지 방식 비교 정리
항목 | associatedtype | 제네릭 | opaque | Existential |
사용 위치 | 프로토콜 내부 | 타입/함수 선언 | 반환 타입 | 변수/매개변수 등 |
타입 결정 시점 | 채택 시 | 컴파일 시 | 컴파일 시 | 런타임 |
다형성 지원 | ✅ 가능 | ❌ 제한적 | ❌ 제한적 | ✅ 완전 지원 |
디스패치 방식 | 정적 + 간접 | ✅ 정적 | ✅ 정적 | ❌ 동적 (느림) |
타입 감춤 | ✅ | ❌ | ✅ | ✅ |
배열 등에 담기 | ❌ | ❌ | ❌ | ✅ 가능 |
🧭 언제 어떤 걸 써야 할까?
상황 | 적합한 방식 |
다양한 타입을 하나의 추상화로 다루고 싶다 | associatedtype + Opaque |
재사용 가능한 자료구조, 알고리즘이 필요하다 | 제네릭 |
프로토콜 반환이 필요한데 associatedtype이 있어서 제한된다 | Opaque |
여러 타입을 하나의 배열 등에 담고 싶다 | Existential |
런타임에 타입이 결정돼야 하고 유연성이 필요하다 | Existential |
성능이 중요하고 타입도 정적이어야 한다 | 제네릭 또는 Opaque |
✅ 마치며, 결국, 이 모든 기능의 목적은?
이 모든 기능은 결국 “좋은 소프트웨어 아키텍처”를 만들기 위해 존재한다.
라고 생각합니다.
이런 추상화 기능들은 각각의 방식으로 우리가 더 유연하고, 안전하며, 유지보수가 쉬운 코드를 작성할 수 있게 도와줍니다.
🎯 추상화를 통해 우리가 얻게 되는 것들
- ✅ 유연한 설계: 타입을 고정하지 않고 바꿔 쓸 수 있는 구조
- ✅ 구현과 인터페이스의 분리: 내부 구현을 몰라도 외부에서 쓸 수 있는 캡슐화
- ✅ 타입 안전성: 컴파일 타임에 오류를 잡아낼 수 있는 강력함
- ✅ 재사용성: 반복되는 패턴 없이 다양한 상황에서 활용 가능
- ✅ 결합도 감소: 모듈 간 의존을 줄이고 테스트/확장이 쉬운 구조
- ✅ 테스트 용이성: 추상화 덕분에 mocking, DI가 쉬워짐
- ✅ 성능과 구조 간의 균형 조율: 런타임 유연성과 정적 최적화 사이에서 선택 가능
🧱 아키텍처 관점에서 본 각 기능의 역할
기능 | 아키텍처적 가치 |
associatedtype | 구현체에 의존하지 않고 유연한 프로토콜 설계 가능 |
제네릭 | 다양한 타입에 대해 재사용 가능, 타입 안정성 유지 |
오파큐 타입 (some) | 구현을 감추면서도 성능을 확보 (SwiftUI에서 핵심적으로 사용됨) |
실존 타입 (any) | 다형성과 런타임 유연성 확보, UI 컴포넌트/플러그인/전략 패턴 등에 유용 |
예전엔 “왜 이렇게 복잡하게 만들었지?” 싶었던 기능들도,
이제는 코드의 구조적인 개선과 실전 유지보수의 편안함을 위해 마련된 장치라는 걸 느끼게 됩니다.
아직 실질적으로 구조화해서 본격적으로 사용할 기회는 많지 않았지만,
앞으로 다가올 상황을 위해 이 개념들을 이해할 필요가 있다고 느꼈습니다.
'TIL' 카테고리의 다른 글
[Swift UIKit] CornerRadius 적용시 의문점 정리 (1) | 2025.05.19 |
---|---|
[Swift] iOS 18.4 를 앞두고 알아야 할 SceneDelegate 기반 앱 구조 전환 (0) | 2025.05.13 |
[Swift Concurrency] 구조적 동시성의 취소(feat: 명시,암시적 취소전파) (0) | 2025.04.18 |
[Swift Concurrency] Task의 취소 (0) | 2025.04.18 |
[Swift Concurrency] Structured Concurrency 구조적 동시성 (0) | 2025.04.15 |