내일배움캠프 37일차

임클·2025년 4월 22일

내일배움캠프

목록 보기
38/44
post-thumbnail

1. 제네릭 개념

제네릭은 함수·타입·열거형·프로토콜 등에 타입 매개변수(type parameter)를 도입해, 여러 구체 타입에 대해 일관된 방식으로 동작하도록 만드는 기능이다.

  • 타입 안정성을 유지하면서 코드 중복을 줄이고, 재사용성을 높일 수 있다.
  • 컴파일 시점에 모든 타입 매개변수에 대해 구체 타입이 결정되므로 런타임 비용이 없다.

2. 제네릭을 사용하는 이유

  1. 코드 재사용성 향상
    • 동일 로직을 여러 타입에 적용할 때 매번 별도 구현이 불필요
  2. 타입 안전성 보장
    • Any 타입 대신 제네릭을 사용하면 컴파일러가 잘못된 타입 사용을 검출
  3. 추상화 수준 상승
    • 비즈니스 로직과 타입 구체화를 분리해, 가독성과 유지보수성 개선
  4. 성능 저하 없음
    • Swift 컴파일러가 제네릭 코드를 구체화(specialization)해, 인라인 확장을 수행

3. 제네릭 타입 파라미터와 제약 조건 설정 방법

3.1 타입 파라미터 선언

  • 함수
    func swapValues<T>(_ a: inout T, _ b: inout T) {
      let temp = a
      a = b
      b = temp
    }
    
  • 타입(구조체/클래스)
    struct Stack<Element> {
      private var items = [Element]()
      mutating func push(_ item: Element) {
        items.append(item)
      }
      mutating func pop() -> Element? {
        return items.popLast()
      }
    }
    

3.2 제약 조건 적용

  • 특정 프로토콜 채택 요구
    func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
      for (i, element) in array.enumerated() where element == value {
        return i
      }
      return nil
    }
    
  • 복합 제약
    func compareItems<T: Equatable & Comparable>(_ a: T, _ b: T) -> Bool {
      return a < b
    }
    
  • where 절 사용
    func allItemsMatch<C1: Collection, C2: Collection>
      (_ c1: C1, _ c2: C2) -> Bool
      where C1.Element == C2.Element, C1.Element: Equatable {
        guard c1.count == c2.count else { return false }
        for (x, y) in zip(c1, c2) where x != y {
          return false
        }
        return true
      }
    

4. 제네릭 사용 장점

  • 재사용성: 한 번 정의한 제네릭 타입·함수를 여러 타입에 적용
  • 안전성: 잘못된 타입 사용 시 컴파일러 오류로 사전 방지
  • 가독성: 중복 코드 감소, 의도 파악이 쉬워짐
  • 성능: 컴파일러가 구체 타입으로 대체해 최적화

5. 제네릭 사용 시 주의할 점

  1. 과도한 추상화
    • 너무 일반화하면 오히려 가독성·유지보수성 악화
  2. 복잡한 제약 조건
    • where 절이 길어지면 코드 이해 난이도 상승
  3. 디버깅 어려움
    • 타입 매개변수 오류 메시지가 다소 난해할 수 있음
  4. 빌드 타임 증가
    • 제네릭 사용이 많으면 컴파일러 최적화 부담 증가

6. 예제 정리

// 스택 예제
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop() as Any)  // Optional(2)

// 제약 조건 예제
let numbers = [3, 1, 4, 1]
if let idx = findIndex(of: 4, in: numbers) {
  print("4의 인덱스:", idx)  // 2
}

// where 절 예제
let a = [1, 2, 3]
let b = [1, 2, 3]
print(allItemsMatch(a, b))  // true

7. 학습 포인트

  • 제네릭 문법과 다양한 제약 조건 문법 숙지
  • 실제 프로젝트에서 적절히 추상화 레벨 결정
  • 디버깅 시 컴파일 오류 메시지 분석 연습

0개의 댓글