Generic (제너릭 프로그래밍) — 매개변수화된 타입
주요 결과물
Generic (제너릭 타입) — 타입 매개변수화를 통해 유연하고 재사용 가능한 코드를 작성할 수 있도록 하는 Swift의 구문 구조입니다. 구체적인 타입 대신 윙 괄호 안에 플레이스홀더를 작성합니다 — 보통
제너릭이 없다면, 각 타입맄다 별도의 함수를 작성해야 합니다: swapInts, swapStrings, swapDates. 하나의 제너릭 swap 함수가 세 가지 변형을 단일 선언으로 대체합니다. 이는 중복을 줄이고, 가독성을 향상시키며, 코드 복사 시 오류 가능성을 늦지다.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Swift 컴파일러는 generic의 각 구체적 사용에 대해 전문화된 코드를 생성합니다. 이는 제너릭 함수 swapValues
제너릭 함수는 하나 이상의 타입 매개변수를 합니다. 타입 매개변수는 함수 이름 뒤에 윙 괄호 안에 지정됩니다:
함수는 여러 개의 제너릭 매개변수를 가질 수 있습니다. 예를 들어, findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
제약 K: Hashable과 where V: Equatable은 findKey가 키가 해쉬블이고 값이 이쿰테이블인 딕셔너리에서만 호출될 수 있도록 보장합니다. 이러한 제약은 행정적인 것이 아니라 필수 요건입니다: Hashable이 없으면 키 검색을 인덱스화할 수 없고, Equatable이 없으면 value를 dictValue와 비교할 수 없습니다.
where — 제너릭 매개변수에 추가 제약을 부과하는 Swift 키워드입니다. 윙 괄호 (
where 절은 함수 또는 타입 시그니처 다음, 여는 중괄호 앞에 작성됩니다. where에서는 T: Comparable & Hashable (동시에 두 프로토콜 준수) 또는 T.U == Int (연관 타입 구체화)를 지정할 수 있습니다.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
where이 있는 확장은 조건을 만족시키는 타입에만 메소드를 추가합니다. Container
Associated Types (연관 타입) — 선언 단계에서 구체적인 타입을 지정하지 않고 프로토콜을 제너릭으로 만드는 방법입니다.
where을 사용하여 다른 프로토콜의 associated types 간에 관계를 설정할 수 있습니다. 예를 들어, 한 프로토콜의 Item이 다른 프로토콜의 Iterator.Element와 일치하도록 요구할 수 있습니다. 이는 컴파일러 레벨에서 타입 호환성을 보장합니다.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack은 typealias를 사용하여 Element를 Int로 구체화합니다. 컴파일러는 이 타입에서 프로토콜의 모든 요구 사항이 충족되는지 확인합니다. associated types이 없다면, 제너릭 프로토콜를 사용하여 StackProtocol
제너릭 확장을 통해 추가 제약 조건을 가진 제너릭 타입에 메소드를 추가할 수 있습니다. 이것은 특정 조건에서만 기능을 혹득하는 조건부 준수 패턴입니다. 서브스크립트도 제너릭이 될 수 있습니다.
서브스크립트는 제너릭이 될 수 있습니다: 윙 괄호 안에서 제너릭 매개변수를 받습니다. 이것은 인덱스가 다른 타입이 될 수 있는 경우, 임의의 인덱스로 컬렉션에 안전하게 접근하는 데 유용합니다.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
이 구조는 강력한 조합을 제공합니다: sum 메소드는 숫자 배열에서만 나타나며, 임의의 인덱스를 사용한 서브스크립트는 정수 인덱스의 모든 시퀀스에서 동작합니다. 컴파일러는 where 조건에 기반하여 이러한 멤버들의 가시성을 관리합니다.
실수는 주로 잘못된 제약 거나 구체적인 타입이 필요한 곳에서 제너릭을 사용하려는 시도와 관련됩니다. 개발자가 지주로 마주처는 세 가지 시나리오를 살펴보겠습니다.
구체적인 타입으로 충분한 곳에 제너릭 매개변수를 추가하는 것은 안티패턴입니다. 함수가 항상 String을 다룬다면
메소드가 존재한다는 것을 지정하지 않고 제너릭 매개변수에서 메소드를 호출하는 것은 오류입니다.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
오류는 명확합니다: 제너릭
자주 묻는 질문
Generic은 Int, String 그리고 다른 타입에 대한 동일한 논리를 중복하지 않고 어떤 타입에서도 동작하는 코드를 작성하는 방법입니다. 구체적인 타입 대신 플레이스홀더
Generic은 컴파일 시간에 구체적인 타입에 대한 정보를 보존하여 컴파일러가 타입을 확인하고 최적화된 코드를 생성할 수 있게 합니다. Any는 타입 소거입니다: 어떤 값이라도 전달할 수 있지만, 타입이 손샤도록 캐스팅(as?)이 필요합니다.
윙 괄호 안에서 T: SomeProtocol 구문을 사용하거나 별도의 where 절 where T: SomeProtocol을 사용하십시오. 제약은 제너릭 매개변수가 특정 메소드와 속성을 지원하도록 보장합니다.
Associated Type (associatedtype)은 프로토콜 내부에서 타입을 위한 플레이스홀더입니다. 프로토콜은 구체적인 타입을 지정하지 않고 associatedtype을 선언하며, 이것이 구현에서 구체화됩니다: Array는 Element, Dictionary는 Key와 Value를 가집니다.
Swift는 제너릭 코드를 각 구체적인 타입에 대한 전문화된 버전으로 컴파일합니다. 실제로 Int에 대한 제너릭 함수는 Int에 대해 직접 작성된 함수보다 늢지 않습니다. 추상화에 따른 오버헤드가 없습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.