-
Swift - GenericProgramming/Swift 2022. 1. 17. 19:30
안녕하세요 BeePeach입니다 :)
오늘은 Generic(제네릭)에 대해서 공부해보려고 합니다.
제네릭이란 단어를 처음 들어봤을 수도 있지만 사실 우리는 계속해서 제네릭을 사용하고 있었습니다.
Array와 Dictionary, Set을 생각해보면 Int를 넣는 배열을 만들기도 하고 String을 넣는 배열을 만들기도 했습니다.
어떠한 Type이 들어가도 다 생성할 수 있었죠.
이렇게 제네릭을 사용하면 Type에 관계없이 유연하고 재사용성이 높은 코드를 작성할 수 있습니다.
Swift에서 제공하고 있는 많은것들도 제네릭으로 구현되어있습니다.
그럼 제네릭을 어떻게 사용하는지 같이 공부해보도록 해요!
Generic
일반적으로 제네릭은 주로 Function, Type에서 사용됩니다.
두 단어가 결합해서 Generic Function, Generic Type이라고 불립니다.
Generic Function이란 Type에 관계없이 사용할 수 있는 func을 말하고
Generic Type은 class, struct, enum 등이 Type과 관계없이 동작하는 것을 말합니다.
살짝 이해가 안간다면 간단한 예를 들어볼게요.
여기서는 Generic Function에 대한 예시만 들어보겠습니다. (다음 포스팅에서 Generic Type에 대해서 알아볼게요.)
Inout 파라미터를 이용해 두 Int의 값을 서로 바꾸는 아주 간단한 함수입니다.
지금까지 우리는 함수나 메서드를 선언할 때 이렇게 파라미터의 Type을 명확하게 정해줬습니다.
별 문제가 없어보이는 코드입니다.
그런데 만약 이 함수를 확장시키고 싶다면 어떻게 될까요??
Double에서도 사용하고싶고 String에서도 사용하고 싶습니다.
아주 간단한 방법이 떠오르네요!
이렇게 만들면 간단하죠??
그런데 너무 비효율적입니다. 이렇게하면 Int32, UInt, Int64, Float ...... 타입마다 모두 다 따로 만들어줘야 합니다.
그럼 또 떠오르는 생각이 있죠.
Any를 이용하면 되겠다! 혹은 protocol에서 공부했듯이 protocol도 Type으로 쓸 수 있으니까 protocol을 전달하면 되지 않을까?? 하는 생각이요..
Swift는 그렇게 호락호락하지 않습니다...
함수를 Any를 이용해 정의하고 String을 변경해보려고 했습니다.
그랬더니 Any면 다른 Type을 넣지 말고 Any를 넣으라고 하네요.
물론 Any타입으로 선언한 변수 2개는 swap이 가능합니다.
하지만 그렇게 되면 Type safe하지 않게 되죠.
Swift는 Type safe한 언어이기때문에 만약 변경된 값의 Type이 다르면 컴파일에러가 발생하게 됩니다.
타입을 프로토콜로 하는 것도 모든 타입을 대표하는 프로토콜이 있는 것도 아니고 결국에는 여러 개를 만들어야 하는 것은 똑같습니다.
그리고 심지어 Custom Protocol은 괜찮은데 Equatable와 같은 프로토콜을 사용해서 함수나 메서드를 만들어보려고 하면 에러가 발생합니다.
(이 문제는 Generic constraint에서 다뤄볼게요.)
이럴 때 이용하는 게 바로 Generic의 Type parameter입니다.
Generic Syntax
일반적인 func 문법과 다른 점을 찾았나요??
FuncName과 Paramater사이에 <TypeParameter>가 추가됐습니다.
이렇게 추가된 Type Parameter(타입 파라미터)는 함수에서 Type대신 사용되는 파라미터입니다.
즉 함수에서 Type이 들어갈 자리에 타입 파라미터가 들어갈 수 있습니다.
그럼 그곳에는 어떠한 Type이라도 들어올 수 있게 됩니다.
함수에서 파라미터가 여러 개 쓰일 수 있는 것처럼 타입 파라미터도 여러 개가 쓰일 수 있습니다.
이때는 ,(comma)로 구분해주면 됩니다.
타입 파라미터의 NamingConvention은 '타입'파라미터이기 때문에 Type을 나타냅니다.
그러므로 UpperCamelCase로 이름을 지어주면 됩니다.
말로 설명하면 이해가 잘 안 갈 수 있으니 바로 예시를 보도록 하겠습니다.
예시를 보면 타입 파라미터 이름을 T로 정했습니다.(명확한 이름이 있다면 정해주면 좋지만 귀찮으면 Type의 앞글자를 따서 T를 많이 사용합니다.)
자체가 Type을 나타내는 것이기 때문에 파라미터를 정하던 것처럼 (a: Int)와 같이 이름, 타입을 따로 지정할 필요가 없습니다.
이름만 적어주면 됩니다.
이전 swap 함수들에서 Int, String이 들어가는 자리에 타입 파라미터인 T가 위치했습니다.
이 의미는 여기에 올 Type이 뭐가 될지는 모르겠으니까 T라고 할게! 라는 말입니다.
이 코드의 경우를 자세히 말해보면
여기서 파라미터로 전달될 a, b의 Type은 뭐가 될진 모르니까 T Type이야!
temp도 T Type이야!
a, b, temp 세 개 모두 아무 Type이나 와도 되는데! 반드시 세 개는 모두 같은 Type 이어야 해!!
라는 말이 됩니다.
그럼 T는 언제 정해줄까요??
일단 뭐가 올진 모르니까 T라고 했는데 우리가 함수를 사용할 때는 결국 Type을 정해줘야 하잖아요??
Generic Function 사용하기
뭔가 사용하기 위해선 엄청난 게 있을 것처럼 설명했지만 사실 그냥 func을 사용하듯 사용하면 됩니다.
파라미터로 전달된 Type으로 자동적으로 적용이 됩니다.
예시에서는 이제 어떤 Type으로 된 값을 전달해도 모두 swapTwoValues(_:_:)로 해결할 수 있는 것을 확인할 수 있습니다.
이렇게 호들갑을 떨며 설명을 한 이유는 func를 호출할 때 <T>가 있던 자리에 사용할 Type을 전달하는 경우가 없도록 하기 위해서입니다..
<T> 타입'파라미터'니까 호출할때 파라미터처럼 전달해줘야지!라고 생각하시면 아래와 같은 에러를 만날 수 있습니다.
Specialization
Generic Specialization이란 override와 비슷한 개념이라고 생각하면 됩니다.
일반적인 구현은 Generic Func을 따르지만 특정 Type일 때는 다른 구현을 하고 싶을 때 사용합니다.
사용 방법은 Func Name과 Parameter를 같게 해 주면 됩니다.
이때 TypeParameter는 사용하지 않습니다. 일반적인 함수를 구현하는 것처럼 구현해주면 됩니다.
이 예시는 똑같은 이름을 가진 swapTwoValues(_:_:)를 정의했습니다.
하지만 파라미터로 전달되는 Type을 String으로 특정했습니다.
이렇게 되면 파라미터로 String Type의 값을 전달할 때만 Generic func이 호출되는게 아니라 여기서 구현한 함수가 호출됩니다.
예제를 보니 String을 전달할때만 로그에 "Special Version"이 호출되는 것을 확인할 수 있습니다.
Swap(_:_:)
사실 지금까지 우리가 구현한 swapTwoValues(_:_:)는 Swift에서 swap(_:_:)이란 이름으로 제공하고 있습니다.
함수 헤더를 확인하면 우리가 사용한 것처럼 타입 파라미터를 이용해서 구현한 것을 확인할 수 있습니다.
만약 두 변수의 값을 바꾸고 싶다면 직접 구현하지 않고 swap(_:_:)을 이용하면 됩니다.
참고자료
https://docs.swift.org/swift-book/LanguageGuide/Generics.html
728x90'Programming > Swift' 카테고리의 다른 글
Swift - Associated Type (0) 2022.01.19 Swift - Generic Type (0) 2022.01.18 Swift - Hashable Protocol (0) 2022.01.16 Swift - Comparable Protocol (0) 2022.01.15 Swift - Equatable Protocol (0) 2022.01.14