11 December 2022

가끔씩, 특정 프로토콜에서 정의된 속성과 함수 이름이 다른 프로토콜에서 겹칠 때가 있습니다.

/// Module: Alpha

public protocol ServiceInterface {
  var value: Int { get set }
  func update()
  func update(value: Int)
}

/// Module: Beta

public protocol ServiceInterface {
  var value: String { get set }
  func update()
  func update(value: Int)
}

두 모듈에서 선언된 ServiceInterface 프로토콜은 value 속성의 타입만 다르며, update 함수는 동일합니다. 이를 구현하는 타입에서는 value의 속성 Int와 String 타입을 둘다 가져야 하는데, 컴파일러는 이를 지원해주지 않습니다.

/// Module: App

struct ServiceImpl: Alpha.ServiceInterface, Beta.ServiceInterface {
  var value: Int
  var value: String

  func update() {}
  func update(value: Int) {}
}

위 코드에서는 에러가 발생합니다. 일반적으로 이러한 경우는 각 프로토콜을 준수하는 별도의 타입을 만들어 해당 타입을 속성으로 사용하는 방식을 사용합니다.

/// Module: App

struct AlphaServiceImpl: Alpha.ServiceInterface {
  var value: Int = 10
  func update() {}
  func update(value: Int) {}
}

struct BetaServiceImpl: Beta.ServiceInterface {
  var value: String = "10"
  func update() {}
  func update(value: Int) {}
}

struct Service {
    let alphaService: Alpha.ServiceInterface
    let betaService: Beta.ServiceInterface
}

let service = Service(alphaService: AlphaServiceImpl(), 
                      betaService: BetaServiceImpl())

@_implements를 이용하여 다른 이름으로 호출하기


Swift에서는 비공식적으로 지원하는 @_implements 속성을 사용하여 다른 이름으로 불리게 할 수 있습니다.

/// Module: App

class ServiceImpl: Alpha.ServiceInterface, Beta.ServiceInterface {
  @_implements(Alpha.ServiceInterface, value)
  var value_Alpha: Int = 2022

  @_implements(Alpha.ServiceInterface, update())
  func update_Alpha() {
    print(#function)
  }

  @_implements(Alpha.ServiceInterface, update(value:))
  func update_Alpha(value: Int) {
    print(#function, value)
    value_Alpha = value
  }

  @_implements(Beta.ServiceInterface, value)
  var value_Beta: String = "2022"

  @_implements(Beta.ServiceInterface, update())
  func update_Beta() {
    print(#function)
  }

  @_implements(Beta.ServiceInterface, update(value:))
  func update_Beta(value: Int) {
    print(#function, value)
    value_Beta = "\(value)"
  }
}

컴파일러는 중간에 코드를 바꿔치기할 수 있어서 의도한 대로 동작하지 않을 수 있습니다. 이러한 경우에는 명시적인 타입을 사용하여 의도한 대로 동작하도록 구현하는 것이 좋습니다.

let serviceAlpha: Alpha.ServiceInterface = ServiceImpl()
print(serviceAlpha.value) // Output : Int(2022)
serviceAlpha.update() // Output : update_Alpha()
serviceAlpha.update(value: 2023) // Output : update_Alpha(value:) 2023
print(serviceAlpha.value) // Output : Int(2023)

let serviceBeta: Beta.ServiceInterface = ServiceImpl()
print(serviceBeta.value) // Output : "2022"
serviceBeta.update() // Output : update_Beta()
serviceBeta.update(value: 2023) // Output : update_Beta(value:) 2023
print(serviceBeta.value) // Output : "2023"



let service = ServiceImpl()
print(service.value) // ❌ value_Alpha, value_Beta 속성을 접근해야 함
print(service.value_Alpha, service.value_Beta) // 🟢 Output: 2022 2022

service.update() // ❌ update_Alpha(), update_Beta() 함수를 사용해야 함
service.update_Alpha() // 🟢 Output: update_Alpha()

service.update(value: 2023) // ❌ update_Alpha(value:), update_Beta(value:) 함수를 사용해야 함
service.update_Beta(value: 2023) // 🟢 Output : update_Beta(value:) 2023


참고자료