본문 바로가기

스폰지밥으로 공부하는 swift/swift 문법

너 T야? 어 제네릭이야~

728x90

제네릭이란?

제네릭은 프로그래밍 언어의 다양한 타입에 대응하여 유연 코드를 만들어주는 친구입니다.

  • 타입의 약자인T를 파라메터 이름으로 사용하지만 다른 이름도 사용 가능해요.
  • 타입 파라미터를 2개이상을 선언하는 것도 가능합니다
  • 구조체, 클래스, 열거형에서도 제네릭이 사용 가능해요.

제네릭의 특징

제네릭의 가장 큰 특징은 바로 '재사용성'과 '타입 안전성'입니다.

  • 재사용성: 같은 로직의 코드를 다양한 타입에 대해 재사용할 수 있게 해줍니다.
  • 타입 안전성: 제네릭을 사용하면 컴파일러가 타입 체크를 할 수 있으므로, 타입 관련 오류를 줄이고 코드의 안전성을 높일 수 있습니다.

좀 딱딱하죠? 바로 스펀지밥 예시로 가시죠!

 

징징이는 집게리아의 캐셔를 맡고 있어요.

현금은 보통 0단위로 떨어지니까 int, 카드는 소숫점 계산이 가능하죠? 마지막으로 외상결제를 String으로 표현해봤습니다.

class 결제 {
    private var 현금수단: Int?
    private var 외상수단: String?
    private var 카드수단: Double?

    init(현금: Int? = nil, 외상: String? = nil, 카드: Double? = nil) {
        self.현금수단 = 현금
        self.외상수단 = 외상
        self.카드수단 = 카드
    }

    func 현금결제하기() {
        if let 현금 = 현금수단 {
            print("결제수단: \(현금)원")
        }
    }

    func 카드결제하기() {
        if let 카드 = 카드수단 {
            print("결제수단: \(카드)원")
        }
    }

        func 외상결제하기() {
        if let 외상 = 외상수단 {
            print("결제수단: \(외상)")
        }
    }
}

이렇게 클래스 내부에 동일한 기능의 이름만 다른 메서드를 작성하게 되는데요.

제네릭을 사용한다면??

class 결제<T> {
    private var 수단: T
    init(수단: T) {
        self.수단 = 수단
    }

    func 결제하기() {
        if let amount = 수단 as? Int {
            print("결제수단: \(amount)원")
        } else if let amount = 수단 as? Double {
            print("결제수단: \(amount)원")
        } else {
            print("안사요~")
        }
    }
}
let 현금결제 = 결제<Int>(수단: 10000)
현금결제.결제하기()  // "결제수단: 10000원" 출력

let 카드결제 = 결제<Double>(수단: 100.50)
카드결제.결제하기()  // "결제수단: 100.5원" 출력

let 외상 = 결제<String>(수단: "(빵을 주며)집게사장님 사랑해요!!")
외상.결제하기()  // "안사요~" 출력

 

요렇게 중복코드 제거가 가능하답니다 ㅎㅎ Int, String, Double 등 다양한 타입을 한번에 처리할 수 있으면서 타입 안전성도 보장해주죠.

제네릭의 장단점

위에서 언급한 재사용성과 타입 안전성을 장점으로 가지고 있지만 단점 또한 존재하는데요,

  • 복잡성: 제네릭을 사용하면 코드가 복잡해질 수 있습니다. 특히 다양한 타입에 대응해야 하는 경우에는 코드의 가독성을 떨어트릴 수 있어요.

제네릭이 주로 사용되는 경우

제네릭은 주로 컬렉션 타입이나, 다른 타입에 의존하지 않는 함수나 메소드를 작성할 때 사용해요.

func exchange<T, U>(a: inout T, b: inout U) {
    let temp = a
    a = b as! T
    b = temp as! U
}
var krabbyPatty = "Krabby Patty"
var price = 1.99
exchange(a: &krabbyPatty, b: &price)

위 예제는 타입이 다른 두 개의 항목을 교환하는 함수인데요, 원래는 제네릭을 사용하지 않으면 컴파일 에러를 일으키게 되죠. 하지만 제네릭을 사용해서 이렇게 간편하게 사용이 가능합니다 ㅎㅎ

inout은 또 뭐죠...? ㅇㅇa..

inout 키워드는 Swift에서 함수의 매개변수를 참조로 전달하기 위해 사용하는 키워드인데요,
기본적으로 Swift에서는 함수의 매개변수로 값을 전달할 때 실제 값이 복사되어 전달되는 Call by Value 방식을 사용합니다. 그래서 함수 내부에서 매개변수의 값을 변경해도, 그 변경이 함수 외부의 원본 데이터에는 영향을 미치지 않죠.

하지만 inout 키워드를 사용하면 참조에 의한 전달(Call by Reference) 방식을 사용할 수 있습니다. 이 방식을 사용하면 함수의 매개변수로 값을 전달할 때 값의 메모리 주소가 전달되며, 따라서 함수 내부에서 매개변수의 값을 변경하면 그 변경이 함수 외부의 원본 데이터에도 영향을 미쳐요.

한줄 요약

제네릭은 필요에 따라 다양한 타입을 받아 코드를 유연하게 작성할 수 있게 해주는 도구라고 할 수 있어요.

조금 더 딥하게 파면 성능적 이점도 있는데 이 부분은 다른 포스팅에서 따로 다뤄보도록 할게요 :)


레퍼런스
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/

 

Documentation

 

docs.swift.org

https://zeddios.tistory.com/226

 

Swift ) Generic

안녕하세요 :) Zedd입니다!백준의 문제를 Swift로 풀고 있는데, 다들 아시다시피 BFS는 그래프 전체를 탐색하되, 인접한 노드들을 차례대로 방문한다는 점에서 주로 Queue로 구현되곤 합니다.DFS를 다

zeddios.tistory.com