jane1choi / TIL

Today I Learned #심야아요

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Swift] Generic

jane1choi opened this issue · comments

서버 통신 시 정말 많이 사용하게 되는 Generic에 대해 Apple의 Swift문서를 참고해서 정리해보려 합니다!

Generic이란?

스크린샷 2022-06-08 오후 8 59 21

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]()

이런 식으로 배열을 생성해줄 때 타입을 명시해주고 싶다면 이렇게 <> 안에 넣어주었습니다!

스크린샷 2022-06-22 오전 12 06 14

사실은 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