[Swift] Generic
jane1choi opened this issue · comments
서버 통신 시 정말 많이 사용하게 되는 Generic에 대해 Apple의 Swift문서를 참고해서 정리해보려 합니다!
Generic이란?
generic은 포괄적인, 통칭의 정도의 사전적인 의미를 가집니다.
그렇다면, Apple에서 소개하는 generic에 대해 한번 살펴보겠습니다!
Generic이란 타입에 의존하지 않는 범용 코드를 작성할 때 사용합니다.
Generic을 사용하면 중복을 피하고, 유연하고 재사용 가능한 코드를 작성할 수 있습니다.
Generic은 Swift의 가장 강력한 기능 중 하나로, Swift 표준 라이브러리의 대부분은 Generic 코드로 작성되어있습니다.
실제로, Generic을 사용했다는 것을 인식 못했다고 하더라도 Language Guide 전체에서 Generic을 사용해왔습니다.
그 예로, Swift의 Array와 Dictionary는 모두 Generic 컬렉션입니다. (타입에 대한 제한없이 Array나 Dictionary를 만들 수 있습니다.)
Generic Function (제네릭 함수)
Apple에서 제공하는 예제를 통해 Generic에 대해 알아보도록 하겠습니다!
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tempA = a
a = b
b = tempA
}
위의 함수는 두 정수의 값을 바꿔주는(swap) 함수입니다.
이 함수로 들어오는 파라미터가 모두 Int
형일 경우에는 문제가 없습니다.
그런데 만약 파라미터 타입이 Int
가 아닌 Double
이나 다른 타입이라면? 사용할 수 없습니다.
왜냐하면 Swift는 타입에 민감한 언어이기 때문입니다!
만약 Int가 아닌 다른 타입에 대해서 swap함수를 사용하고 싶다면
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let tempA = a
a = b
b = tempA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let tempA = a
a = b
b = tempA
}
위와 같이 파라미터 타입을 바꿔 필요에 따라 함수를 여러개 만들어 주면 되지만..
이 과정이 너무나 번거롭겠죠..? 이렇게 타입에 제한을 두지 않는 코드를 사용하고 싶을 때 사용하는 것이 바로 Generic입니다!
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let tempA = a
a = b
b = tempA
}
위와 같이 꺽쇠(<>)를 이용해서 안에 타입 처럼 사용할 이름(위의 예제에선 T)을 선언해주면,
그 뒤로 해당 이름(T)을 타입처럼 사용할 수 있습니다.
여기서 이 T를 Type Parameter라고 부르는데, T라는 새로운 형식이 생성되는 것이 아니라,
실제 함수가 호출될 때 해당 매개변수의 타입으로 대체되는 placeholder이기 때문에 Swift는 "T"라는 실제 타입을 찾지 않습니다.
그렇기 때문에 T말고 다른 타입 이름도 다 사용가능합니다!
+ 타입 파라미터 이름은 어떤 이름이든 자유롭게 선언 가능하지만,
보통 가독성을 위해 T나 V 같은 단일 문자, 혹은 Upper Camel Case를 사용한다고 합니다!
var someInt = 1
var anotherInt = 2
swapTwoValues(&someInt, &anotherInt) // 함수 호출 시 T는 Int 타입으로 결정됨
var someString = "Hi"
var anotherString = "Bye"
swapTwoValues(&someString, &anotherString) // 함수 호출 시 T는 String 타입으로
위와 같이 제네릭으로 선언한 함수는
이렇게 실제 함수를 호출할 때, Type Parameter인 T의 타입이 결정됩니다!
swapTwoValues(&someInt, &anotherString) // Cannot convert value of type 'String' to expected argument type 'Int'
그런데 만약 위와 같이 서로 다른 타입을 파라미터로 전달하면 에러가 발생하게 됩니다.
왜냐하면 위에서 함수 정의 시 파라미터 a와 b 둘다 같은 타입 파라미터인 T로 선언했기 때문입니다!
이렇게 서로 다른 타입을 파라미터로 전달하면, 첫 번째 someInt를 통해 타입파라미터 T가 Int로 결정됐기 때문에,
두 번째 파라미터인 anotherString의 타입이 Int가 아니라며 에러가 나는 것입니다!
func swapTwoValues<One, Two> { ... }
타입 파라미터는 ,를 이용하여 여러 개를 선언해줄 수도 있습니다!
이렇게 제네릭 함수를 잘 이용하면 똑같은 내용의 함수를 오버로딩 할 필요가 없어 코드 중복을 피하고 유연하게 코드를 짤 수 있습니다!
Generic Type (제네릭 타입)
제네릭은 함수에만 가능한 것이 아니라,
구조체, 클래스, 열거형 타입에도 선언할 수 있는데, 이것을 제네릭 타입(Generic Type) 이라고 합니다!
만약 Stack을 제네릭으로 만들고 싶다면,
struct Stack<T> {
let items: [T] = []
mutating func push(_ item: T) { ... }
mutating func pop() -> T { ... }
}
이렇게 제네릭 타입으로 Stack을 선언할 수 있습니다. (클래스, 열거형 또한 가능합니다)
제네릭 타입의 인스턴스를 생성할 때는
let stack1: Stack<Int> = .init()
let stack2 = Stack<Int>.init()
이렇게 선언과 마찬가지로 <>를 통해 어떤 타입으로 사용할 것인지를 명시해주어야 합니다.
요 선언 형식 어딘가 익숙한데..어디서 봤냐면..!!
var integers: Array<Int> = [Int]()
이런 식으로 배열을 생성해줄 때 타입을 명시해주고 싶다면 이렇게 <> 안에 넣어주었습니다!
사실은 Swift의 Array는 제네릭 타입이었네요!
Generic을 사용했다는 것을 인식 못했다고 하더라도 Language Guide 전체에서 Generic을 사용해왔습니다.
이게 요런 뜻이었던 것..!
참고 자료: https://docs.swift.org/swift-book/LanguageGuide/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_283, https://zeddios.tistory.com/226, https://babbab2.tistory.com/136