스위프트_제네릭(Generic in Swift)
21 Feb 2018 | swift 스위프트 제네릭 GenericSwift Generic
스위프트의 제네릭을 이용해서 다양한 타입이 같은 방식으로 동작하는 코드를 작성할 수 있다.
제네릭은 추상화 단계에서, 만약 코드에 동작하는 로직이 똑같은데 적용돼야 할 타입이 여러개면 제네릭을 사용하는 것이 좋다.
Stack을 제네릭으로 만들기
// Element : placeholder type
struct Stack<Element> {
var items = [Element]()
mutating func push(_ newItem: Element) {
items.append(newItem)
}
mutating func pop() -> Element? {
guard !items.isEmpty else {
return nil
}
return items.removeLast()
}
}
// execute Stack<Int>
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop()) // Optional(2)
- 여기서 사용된
Element
는 placeholder타입으로, 일종의 자리 맡기 타입 이다. - 제네릭이 선언된 구조체를 사용할 때는 코드 하단부의 execute 부분처럼, 사용할 타입을
<>
안에 기재해준다.
만약 예시처럼Stack<Int>
로 선언했다면, Stack구조체 안에서Element
로 선언된 모든 부분이Int
로 적용되어서 동작하게된다.
map함수를 제네릭으로 구현하기
<T,U>
: 자리 맡기용 타입, placeholder_ items: [T]
: 입력(파라미터)배열, 배열의 value의 타입은 T_ f: (T) -> (U)
: 클로저 선언부로, T타입의 인수를 받아 U타입으로 변환한다는 뜻-> [U]
: 리턴값은 배열, 그 배열의 value 타입은 U
// f: 부분은 클로저 선언부
func myMap<T,U>(_ items: [T], _ f: (T) -> (U)) -> [U] {
var result = [U]()
for item in items {
result.append(f(item))
}
return result
}
// execute myMap()
let letters = ["one","two","three"]
let lengths = myMap(letters) { $0.count }
print(lengths) // [3,3,5]
- 해당 코드에서 myMap()함수는 String배열을 받아서 각 value의 글자 수를 세어 Int배열을 리턴하는 용도로 사용되었다.
- myMap함수를 호출할때 괄호 안에 기재된 파라미터 letters는 함수 선언부에서의
_ items: [T]
를 의미한다. - execute부분에서 볼 수 있듯이,
{ $0.count }
가 바로 위에서 설명된 클로저 선언부이다.String
타입인 value각각을 뜻하는$0
의.count -리턴값은 Int-
를 호출하여[U]
타입 리턴값에 추가한다.
타입 제한 조건
제네릭은 어떤 타입도 올 수 있다는 장점이 있다. 하지만 이렇게 앞으로 사용될 구체적 타입을 컴파일러가 알지 못하는 개방성때문에 오히려 사용에는 제한적인 면이 있다. 이를 보완하기 위해 아래와 같은 두 가지 타입 제한 조건을 둔다.
- 타입은 어떤 클래스의 서브 클래스가 되어야 한다.
- 타입은 프로토콜을 준수해야한다.
한 가지 예시로, 딕셔너리의 Key값이 되는 타입은 Hashable 프로토콜을 반드시 준수해야하는 조건이 있는데, 이는 물론 제네릭의 타입제한조건때문이다.
이러한 타입제한조건은 제네릭을 사용하는 콜렉션들이 더 견고해지고 정확하게 동작할 수 있게 만든다. 딕셔너리의 Key가 Hashable 프로토콜을 준수해야하는 제한 조건을 둠으로써 딕셔너리의 key값 하나하나는 고유한 값이 되어 value들을 대표할 수 있게 되는것이다.
타입 제한 조건을 적용하여 동일성 판단
- 프로토콜을 준수하는 타입 T를 사용하여 파라미터의 값이 같은지 판단하는 함수를 만들었다.
- 프로토콜을 준수하는 placeholder타입은
<T: Equatable>
와 같이 선언하면 되고, 상속일때도 마찬가지다.
func checkEqual<T>(_ first: T, _ second: T) -> Bool {
return first == second
} // Error: Binary operator '==' cannot be applied to two 'T' operands
func newCheckEqual<T: Equatable>(_ first: T, _ second: T) -> Bool {
return first == second
}
- 이 코드의 위쪽에 선언된 checkEqual() 함수는 에러가 발생한다. T가 어떤 타입이 올지 컴파일러는 예측할 수 없기 때문에 비교연산자가 작동하는지 장담할 수 없어서 에러를 발생시킨다.
- newCheckEqual()을 보면 placeholder로 T가 선언되긴했지만 그 T는 Equatable을 준수하는 타입만 올 수 있기때문에 컴파일러는 비교연산자가 작동할 것임을 알 수 있다.
타입 제한 조건을 적용한 동일성 판단 두번째
- 프로토콜을 사용해서 T,U 타입이 달라도 비교가 가능하도록 만들 수 있다.
func checkIfDescriptionMatch<T: CustomStringConvertible, U: CustomStringConvertible>(_ first: T, _ second: U) -> Bool {
return first.description == second.description
}
print(checkIfDescriptionMatch(Float(1.0), Double(1.0)))
Comments