Using Existential and Generic feat.Dispatch

2024. 9. 17. 23:34iOS/Swift 문법

Tuist를 통한 모듈화를 하면서 build 최적화에 관심을 가지게 되었다. 그 중에 하나가 static dispatch와 dynamic dispatch이다.

단순하게는 class 선언 시 final을 붙이거나, property에서 private을 붙여 최적화하는 정도만 알았다.

다음의 짧은 아티클을 보면서 Protocol을 통한 type 선언 시, dynamic dispatch가 되는 상황을 알아보자!

Protocol

Swift는 protocol을 통해 기능과 특징을 정의할 수 있다. 아래는 polinator 역할을 protocol로 정의하고, Insect와 Bee가 이 역할을 수행하는 예시이다.

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

struct Hummingbird: Pollinator {
    func pollinate(_ plant: String) {
        print("\(plant) pollinated by a hummingbird's bill.")
    }
}
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")

Generic - Static Dispatch

Generic을 이용하여 polinator를 파라미터로 받아 처리하는 func을 작성할 수 있다.

func polinate<T: Polinator>(_ plants: [String], with pollinator: T) {
    for plant in plants {
       pollinator.pollinate(plant)
    }
}
polinate(["Rose", "Thistle"], with: speedy)
polinate(["Prickly Pear"], with: busyBee)

이 때, Swift는 static dispatch를 이용하여 함수 호출을 최적화한다. 컴파일러는 speed는 HumingBiard type이고 func을 수행할 때, 허밍버드 구현부를 이용해야한다는 것을 안다.

Existential type - Dynamic Dispatch

flexibility를 위해 변수에 specific한 타입이 아닌 any를 이용하여 타입을 지정할 수 잇다.

var anotherPolinator: any Pollinator = Humingbird()
anotherPollinator.pollinate("Dandelion")

anotherPollinator = Insect()
anotherPollinator.pollinate("Dandelion")

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

polinate2(["Lilly", "Gardenia"], with: anotherPollinator)

이 때는, compile time 때 type을 지워고, run time 때 타입을 결정한다. 즉, dynamic dispatch를 사용한다. 코드의 유연성을 가져갈 수 있지만, 남용하면 dynamic dispatch를 사용하여 성능에 안 좋을 수 있다. 이러한 extra bookeeping으로 인한 memory을 더 점유하기 때문에 static dispatch를 이용하는 generic에 비교해서 덜 효율절이다.

Dependency Injection

우리는 Dependency Injection할 때 protocol을 type으로 사용한다. Test와 Dev, Release에서 다른 결과를 얻기 위함이다. 하지만 Dependency Injection 또한 필요할 때만 적절하게 사용해야하고, trade off가 있을 수 있다는 점을 알고 사용해야한다.

 

Swift6에서는 이러한 남용을 막고자, 이전에는 protocol을 type으로 사용할 경우, any를 붙이라고 warning만 하였지만, 6.0 이후에는 any를 쓰지 않으면 에러가 발생한다.