제네릭(Generic)
정의
코드의 유연성 및 재사용성을 높이기 위한 추상화 기법. 코드 중복 방지, 의도를 명시적으로 드러낼 수 있다.
사용용도
- 표준 라이브러리를 만드는 핵심 기법이다.
- Array, Dictionary 타입이 generic collection.
- Array : Int, String, ... 등등 타입을 사용 가능
제네릭 함수
- 2개 정수를 입력 받아 서로 값을 바꾸는 예제
func swapInts(inout a: Int, inout _ b: Int) {
let tempA = a
a = b
b = tempA
}
var firstInt = 2
var secondInt = 3
swapInts(&firstInt, &secondInt)
- 2개 문자열을 입력 받아 서로 값을 바꾸는 예제
func swapStrings(inout a: String, inout _ b: String) {
let tempA = a
a = b
b = tempA
}
- 타입에 상관없이 동일 타입의 2개 값을 받아서 서로 값을 바꾸는 예제
func swapValues<T>(inout a: T, inout _ b: T) {
var tempA = a
a = b
b = tempA
}
예제
func swapValues<T>(inout a: T, inout _ b: T)
{
let temporaryA = a
a = b
b = temporaryA
}
타입 인자(Type parameter)
- 위에 예제에서
T를 타입 인자라고 부른다. - 함수 바로 뒤에
<T>와 같은 형태로 사용한다.
타입 인자 이름 붙이기
- Dictionary
- 에서 Key, Value와 같이 타입 인자에 속성을 설명하는 이름을 붙인다.
- 타입 인자와 제네릭 타입 사이의 관계를 코드를 읽는 사람 이해를 돕는다.
- 전통적으로
T, U, V와 같이 단일 대문자로 표현한다. - 주의 : 타입이라는 것을 표시하기 위해서 대문자를 사용한다.
제네릭 타입
- 제네릭 함수를 위해서 봤고 이번에는 개발자가 직접정의할 수 있는 제네릭 타입에 대해서 알아보자.
- class, struct, enum와 함께 사용된다. (Array, Dictionary)
- Stack이라는 제네릭 콜렉션 타입 예제

- 현재 Stack에 3개 값이 있다.
- 4번째 값을 추가해서 맨 위에 위치하게 된다.
- 이제 Stack은 총 4개 값을 가지고 있다.
- pop을 호출해서 Stack 맨 위에 있는 값을 빼낸다.
- pop을 하고 난 후에 1에서와 같이 3개 값을 가지고 있는 상태가 된다.
- 우선 제네릭이 아닌 Int 값에 대한 Stack을 구현해 보자.
struct IntStack {
var items = [Int]()
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
- items는 Array 프로퍼티로 Int 값을 저장하는 역할을 수행한다.
- push와 pop 메소드를 제공한다.
- 돌발 퀴즈 : 왜 mutating로 지정하였을까?
- 답 : 메소드가 프로퍼티의 값을 변경하는 경우 mutating 키워드를 붙여줘야 한다.
- 자 이제 Int 타입뿐만 아니라 다양한 타입을 가지는 제네릭 Stack을 구현해 보자.
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("cat")
stackOfStrings.push("dog")
stackOfStrings.push("cow")

let fromTheTop = stackOfStrings.pop()

- 코드 읽기
- items 프로퍼티는 Element 타입의 값을 가지는 Array 값이다.
- push 메소드는 Element 타입의 item을 배열에 넣는다.
- pop 메소드는 Element 타입의 값을 반환한다.
제네릭 타입 확장
- 기존 제네릭 타입을 확장하기
- 아래 예제와 같이
extension을 붙인다. - 기존 제네릭 타입의 타입 인자를 그대로 사용할 수 있다.
- 아래 예제와 같이
- 읽기 전용 프로퍼티인 topItem
- pop하지 않고 최고 위에 있는 값을 반환한다.
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count-1] } } - Quiz :
?를 사용한 이유는? - 답 : nil이 될 수 있는 경우 사용. isEmpty에 따라 nil을 반환할 수 있기 때문이다.
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
타입 제약
- 이전 제네릭 swap 함수를 봤다. 어떤 값이든 올 수가 있었다. 하지만 특정 타입에 제약을 걸고 싶다면?
- Dictionary 타입의 경우 key에 제한이 걸려있다. 이 제약은 hashable해야한다는 것이다. key의 속성은 유일무이해야 하며 포함하고 있는지 여부를 바로 확인할 수 있어야 한다.
- 참고로 Swift의 기본 타입들 모두 hashable하다.(String, Int, Double, Bool)
문법
func someFunction<T: SomeClass, U: SomeProtocol> (someT: T, someU: U) {
}
- 2개 타입 인자를 가진다. 첫번째는 T이며 SomeClass의 서브클래스라는 제약을 가지고 있다. 두번째는 U이며 SomeProtocol을 만족시켜야 한다.
타입 제약 동작
- 일반 함수 findStringIndex 함수가 아래와 같다.
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
for(index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findStringIndex(strings, "llama") {
print("The index of llama is \(foundIndex)")
}
- 제네릭 함수 findIndex를 만들어 보자.
func findIndex<T>(array:[T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
- 위 코드는 컴파일 에러가 발생한다?
- Quiz : 왜 컴파일 에러가 발생하는가?
- 답 : if value == valueToFind 부분에서
- Equatable 성질을 가지도록
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
for(index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
- 타입 T는 Equatable 프로토콜을 만족시켜야 한다는 제약을 가지도록 했다. Equatable 속성을 가지는 Double과 String이 가능하다.
Quiz
- 정의에 대한 질문
- 용도에 대한 질문
- 예제 관련