본문 바로가기
FRONT-END/iOS

Swift 제네릭(Generics) 문법 알아보기

by 랄라J 2023. 8. 11.

제네릭 문법

  • 형식에 관계없이, 한 번의 구현으로 모든 타입을 처리하여 타입에 유연한 함수를 작성하는 문법이다.
  • 유지보수 및 재사용성이 증가하는 장점이 있다.
  • 함수뿐만 아니라 구조체, 클래스, 열거형도 제네릭으로 일반화가 가능하다.

 

제네릭 문법이 필요한 이유

  • 타입만 다르고 구현 내용이 동일한 경우, 제네릭이 없다면 타입마다 모든 경우를 다 정의해줘야 한다. 개발자의 일이 늘고, 유지보수 및 재사용성이 어렵게 만들기 때문이다.

 

제네릭 함수의 정의

func genericsFunc<T>(array: [T]){
  // code
}
  • <T>는 타입 파라미터라고 한다. 함수 내부에서 파라미터의 타입이나 리턴형으로 사용된다.
  • 관습적으로 Type의 의미인 T를 사용하기도 하지만, 다른 문자를 사용해도 된다. (형식 이름이기 때문에 UpperCamelCase로 선언)
  • <T, U>처럼 타입 파라미터를 2개 이상 선언하는 것도 가능하다.

 

구조체 제네릭으로 정의하기

struct GenericMember<T> {
    var members: [T] = []
}

let numMember = GenericMember(members: [1, 2, 3])
let strMember = GenericMember(members: ["안녕", "하이"])

 

클래스 제네릭으로 정의하기

class GridPoint<A> {
    var x: A
    var y: A
    
    init(x: A, y: A){
        self.x = x
        self.y = y
    }
}

let aPoint = GridPoint(x: 10, y: 20)
let aPoint = GridPoint(x: 3.2, y: 5.3)

 

열거형 제네릭으로 정의하기

enum Pet<T> {
    case dog
    case cat
    case bird
    case etc(T)
}

let myPet = Pet.cat
  • 열거형은 연관값을 가질 때 제네릭으로 정의할 수 있다.

 

제네릭 확장

struct Coordinates<T> {
    var x: T
    var y: T
}

extension Coordinates {
    func getPlace() -> (T, T) {
        return (x, y)
    }
}

extension Coordinates where T == Int {
    func getPlace() -> (T, T) {
        return (x, y)
    }
}
  • 확장 시에도 기존에 구현되어 있던 타입 파라미터를 활용해 위와 같이 정의할 수 있다.
  • where 절을 활용해 특정 타입일 때만 확장되도록 설정할 수 있다.

 

타입 제약(Type Constraint)

func findIndex<T: Equatable>(item: T, array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if item == value {
            return index
        }
    }
    return nil
}
  • 제네릭에서 타입을 제약할 수 있다.
  • 타입 매개 변수 이름 뒤 콜론으로 프로토콜 제약 조건 또는 단일 클래스를 배치할 수 있다.

 

구체 / 특정화(specialization) 함수 구현 가능

func findIndex(item: String, array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
    	if item.caseInsensitiveCompare(value) == .orderedSame {
            return index
        }
    }
    return nil
}
  • 위와 같이 동일한 함수 이름에 구체적인 타입을 명시하면, 해당 구체적인 타입의 함수가 실행된다.

 

프로토콜에서 제네릭의 사용 (Associated Types)

protocol RemoteControl {
    associatedtype Element
    func changeChannel(to: Element)
    func alert() -> Element?
}

struct TV: RemoteControl {
    typealias Element = Int // 생략 가능
    
    func changeChannel(to: Int) {
    	print("TV 채널바꿈")
    }
    
    func alert() -> Int? {
        return 1
    }
}
  • <T>처럼 기존의 타입 파라미터 형식이 아닌 associatedtype T 형태로 정의한다.
    • 프로토콜에서는 T 대신 Element을 관습적으로 많이 사용하고 있다
    • 프로토콜은 타입들이 채택할 수 있는 요구사항만을 선언하는 개념이기 때문에 제네릭 타입과 조금 다른 개념이 도입된 것으로 추정된다.
  • 프로토콜을 채택하는 쪽에서는 구현해야하는 함수에 타입을 정확히 명시하면 된다. 또는 typealias로 실제 형식을 표시해야한다.
728x90

댓글