새소식

인기 검색어

iOS/Swift

[Swift] Generics(제네릭)

  • -

 

오늘 알아볼것은 제네릭(Generics) 문법이다.

 

제네릭(Generics)이란 ?

 

shttps://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics

 

공식문서를 보면

 

다양한 타입에 실행되는 코드를 작성하고 해당 유형의 요구사항을 지정한다고 나와있다.

 

왜 제네릭 문법이 필요한지 알아보자.

 

제네릭의 필요성

 

Int타입의 배열을 출력해주는 함수가 있다.

 

 

해당함수는 Int 타입의 배열을 출력가능하지만

String 타입의 배열을 출력이 불가하다

 

 

 

String 배열을 출력하기 위해선 

매개변수의 타입을 String타입으로 변경한 함수가 필요하다.

 

 

하지만 다른 타입의 변수가 생긴다면 또 다시

해당 타입에 맞는 함수를 다시 작성해주어야 한다

 

타입별 printArray

 

이때 드는 생각은

타입만 다르고 구현내용은 완전히 동일한데

굳이 코드를 반복할 필요가 있을까

타입을 매개변수로 받아서 타입만 변경하면 되지 않을까?

라는 아이디어에서 나온것이 제네릭이다.

 

제네릭의 사용법

 

제네릭은 제네릭문법을 적용시키고싶은 곳의 이름 옆에 <T> 를 붙여준뒤

해당 함수나 구조체, 클래스등에서

타입을 변경할 곳에 T를 써준다

 

배열의 타입을 바꾸고 싶기때문에 [T]

 

// 제네릭 문법을 적용한 구조체
struct GenericObject<T> {
    var objects: [T] = []
}

let object1 = GenericObject(objects: [1, 2, 3, 4, 5])
let object2 = GenericObject(objects: ["1", "2", "3", "4", "5"])

 

사실 제네릭문법은 <T> 말고

<> 안에 어떤 문자든 사용가능하다. 대신 <>안에 있는 문자를

제네릭 문법을 적용할 곳에는 동일하게 작성해주어야 한다.

단 숫자만은 불가능하다.

 

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

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

// 한글도 가능하다
class GenericPoint3<타입> {
    var x: 타입
    var y: 타입
    
    init(x: 타입, y: 타입) {
        self.x = x
        self.y = y
    }
}

// 숫자만은 불가능
class GenericPoint3<3> {
    var x: 3
    var y: 3
    
    init(x: 3, y: 3) {
        self.x = x
        self.y = y
    }
}

 

제네릭 문법을 사용했다고 해서 전부 제네릭타입으로 선언하지 않아도 된다.

// 전부 제네릭타입으로 작성하지 않아도 된다.
class GenericColor<T> {
    var red: T
    var green: UIColor
    var blue: T
    ...
}

 

열거형에서도 사용가능하다

 

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

let animal = Pet.etc("햄스터")


enum Computer<T> {
    case cpu(core: T)
    case ram(size: Int)
}

let com1 = Computer.cpu(core: "Hex")
let com2 = Computer.cpu(core: 16)

 

제네릭의 확장

 

제네릭 타입을 채택한 구조체나 클래스도 확장이 가능하다.

확장에서 타입파라미터를 사용하여도 타입 파라미터를 명시하지 않는데

그 이유는 본체에서 정의한 타입 파라미터를 사용하기 때문이다.

 

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

// 확장시에는 타입파라미터<T>를 명시하지 않는다
// 본체의 제네릭에서 정의한 타입 파라미터를 사용하기 때문
extension Point {
    func getPoint() -> (T, T) {
        return (x, y)
    }
}

// where절도 사용가능하다.
extension Point where T == Int {
    func distancePointToZero() -> T {
        return (x * x) + (y * y)
    }
}

 

제네릭의 타입 제약

제네릭에서 타입을 제약할수도 있다.

제네릭 선언시에 제약조건을 명시하면 된다.

 

<T: 제약조건> 

 

// == 연산자를 사용하기 위해선 Equatable 프로토콜을 채택해야 한다
func Equal<T: Equatable>(_ x: T, _ y: T) -> Bool {
    x == y
}

 

특정 클래스를 상속한 클래스만 사용가능하도록 제약을주거나

 

class Person {}
class Professor: Person {}
class Student: Person {}

let person = Person()
let professor = Professor()
let student = Student()

// 특정 클래스를 상속받은 클래스만 사용가능
func personClassOnly<T: Person>(array: [T]) {
}

personClassOnly(array: [person, person])
personClassOnly(array: [professor, professor])
personClassOnly(array: [student, student])

 

특정 프로토콜을 채택한 타입만 사용가능하도록 제약을 줄 수도 있다.

 

protocol Person {}

class Professor: Person {}
class Student: Person {}


let professor = Professor()
let student = Student()

// 특정 프로토콜을 채택한 타입만 사용할 수 있다는 제약
func personProtocolOnly<T: Person>(array: [T]) {

}

personProtocolOnly(array: [professor, professor])
personProtocolOnly(array: [student, student])

 

프로토콜에서의 제네릭

 

프로토콜에서도 제네릭 타입을 사용할 수 있는데

프로토콜에서는 associatedtype(연관타입)이라는 것을 사용하여

제네릭과 동일한 타입 파라미터를 저장한다.

 

protocol Person {
    associatedtype T
    func doSomeThing(_ something: T) -> T
}

class Professor: Person {
    typealias T = String
    func doSomeThing(_ something: String) -> String {
        ...
    }
}
class Student: Person {
    typealias T = Int
    func doSomeThing(_ something: Int) -> Int {
        ...
    }
}

 

당연하게도 제약사항이나 T대신 다른이름을 사용할 수 있다.

 

protocol Person {
    associatedtype Element: Comparable
    func doSomeThing(_ something: Element) -> Element
}

 

 

제네릭 -끗-

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.