Swift

Using existentials and generics(익스텐셜 / 제네릭 타입)

ziziDev 2024. 8. 6. 15:36
반응형

Using existentials and generics

 

 

Comparing existential and generic types

스위프트 프로토콜들은 다양한 타입을 채택할 수 있는 기능과 특성을 정의할 수 있습니다

예를들어 아래에 있는 것처럼 두 방식의 프로토콜을 볼 수 있습니다

protocol Pollinator {
    func pollinate(_ plant: String)
}

//Pollinator protocol 채택하면 반드시 구현해야함
struct Hummingbird: Pollinator {
    func pollinate(_ plant: String) {
        print("\(plant) pollinated by a hummingbird's bill.")
    }
}

//Pollinator protocol 채택하면 반드시 구현해야함
struct Insect: Pollinator {
    func pollinate(_ plant: String) {
        print("\(plant) pollinated by an insect's legs.")
    }
}


let speedy = Hummingbird()
let busyBee = Insect()
speedy.pollinate("Daisy")
busyBee.pollinate("Marigold")
// Daisy pollinated by a hummingbird's bill.
// Marigold pollinated by an insect's legs.

 

 

프로토콜은 채택되는 순간 구체화를 해야합니다

하지만 human insect 다른 객체에 다르게 구체화할 수 있습니다

프로토콜은 유형을 정의하고 다음과 같이 제네릭 입력으로 받는 함수를 작성할 수 있습니다 

func pollinate<T: Pollinator>(_ plants: [String], with pollinator: T) {
    for plant in plants {
        pollinator.pollinate(plant)
    }
}
pollinate(["Rose", "Thistle"], with: speedy)
pollinate(["Prickly Pear"], with: busyBee)
// Rose pollinated by a hummingbird's bill.
// Thistle pollinated by a hummingbird's bill.
// Prickly Pear pollinated by an insect's legs.

 

 

이 경우에 스위프트 함수 호출을 최적화하기 위해 정적 디스패치를 사용할 수 있습니다 컴파일러는 speedy가 hummingbird라는 것을 인식하므로 pollinate함수느 모든 식물에 대해 humingbird의 pollinate구현을 사용하고 있습니다

 

제네릭 함수를 사용하게 되면 정적 디스패치를 사용하여 컴파일 타임에 타입을 결정하고 이에 따라 성능이 최적화 됩니다

 

더 유연하게 사용하고 싶다면 polinator를 any 타입 변수를 생성할 수 있습니다 

var anotherPollinator: any Pollinator = Hummingbird()
anotherPollinator.pollinate("Dandelion")
// Dandelion pollinated by a hummingbird's bill.
anotherPollinator = Insect()
anotherPollinator.pollinate("Dandelion")
// Dandelion pollinated by an insect's legs.

위 예제에서 any 키워드를 사용해서 곤충이나 벌새를 anotherPollinator에 할당을 할 수 있습니다

어느 경우든 pollinate 함수를 호출할 수 있는데 두 유형 모두 polinator를 준수하고 그 역할을 수행할 수 있기 때문입니다 

 

비슷한 방식으로 any pollinate 인풋으로써 메서드를 작성할 수 있습니다 

 

func pollinate2(_ plants: [String], with pollinator: any Pollinator) {
    for plant in plants {
        pollinator.pollinate(plant)
    }
}

// 함수 호출 예시
var anotherPollinator: any Pollinator = Hummingbird()
pollinate2(["Lilly", "Gardenia"], with: anotherPollinator)
// Lilly pollinated by a hummingbird's bill.
// Gardenia pollinated by a hummingbird's bill.

anotherPollinator = Insect()
pollinate2(["Lilly", "Gardenia"], with: anotherPollinator)
// Lilly pollinated by an insect's legs.
// Gardenia pollinated by an insect's legs.

 

이 함수는 코드가 실행하는 동안 벌새 / 곤충 받아들일 수 있는 인스턴스를 모두 입력받을 수 있습니다

추가된 유연성의 비용을 고려해야합니다 existential types 일반적으로 런타임에 작동을 하는데 Swift는 기본 타입 정보를 지우고 동적 디스패치를 사용해서 코드를 수행하고 있습니다

이러한 작업은 정적 디스패치를 사용하는 제네릭 매개변수보다 더 많은 메모리를 필요로 하고 있습니다

 

기본 타입 정보를 지우고 동적 디스패치를 사용하기 때문에 추가적인 메모리 계산 작업이 필요하기 때문에 약간은 성능 저하와 더 많은 메모리 사용이 발생할 수 있습니다

 

이런 익스텐셜 타입 유형을 사용하게 되면 다양한 객체를 유연하게 처리할 수 있지만 성능과 메모리 사용에 대한 추가 비용이 발생할 수 있습니다 이를 고려해서 둘 중에 적절한 상황에 사용해야합니다

 

결론적으로 제네릭 타입과 익스텐셜 타입 유형의 차이점을 이해하는게 중점이였던것 같다

제네릭 타입은 성능과 타입 안정성에서 이점을 제공하고 있지만 유연성이 떨어지고 있다고 말하는것 같았다

하지만 익스텐셜 타입은 유연성을 제공하지만 성능 저하와 추가적인 메모리 사용이 발생할 수 있다

 

 

 

 

 

 

반응형