Swift: some, Any

틀틀보·2025년 6월 29일

Swift

목록 보기
10/19

some

불투명 타입(Opaque Type)으로 내부의 구체 타입은 컴파일러가 알고 있으나, 외부에는 숨기는 프로토콜

왜 사용할까?

1. 구현 감추기 + 정적 디스패치

protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    let radius: Double
    func area() -> Double {
        return .pi * radius * radius
    }
}

struct Square: Shape {
    let side: Double
    func area() -> Double {
        return side * side
    }
}

// some을 이용해 반환 타입을 숨기고 정적 디스패치를 보장
func makeCircle(_ radius: Double) -> some Shape {
    return Circle(radius: radius)
}

func makeSquare(_ side: Double) -> some Shape {
    return Square(side: side)
}
  • 호출자에게는 Shape 프로토콜만을 보여 구현을 숨길 수 있음.

  • 각각의 makeCircle, makeSquare 메서드는 컴파일 단계에 구현 타입이 고정됨.

  • 컴파일 단계에서 구현 타입이 고정되기에 속도와 최적화 면에서 유리

  • 컴파일러는 이미 Circle 타입인 걸 알고 있으며, 코드 작성자 입장에서는 Shape로 추상화되어 작성 가능

2. 연관 타입 처리 가능

protocol ImageFetching {
    associatedtype ImageType
    func fetch() -> ImageType
}

func makeFetcher() -> some ImageFetching {
    RemoteFetcher() // RemoteFetcher: ImageFetching 구현체
}
  • associatedtype을 포함한 프로토콜 반환 시, some Protocol을 사용하여 연관 타입 정보는 유지되면서 내부 타입은 숨김

3. 제네릭 코드 간소화

// 제네릭 버전: T 타입에 제약을 걸어 함수 정의
func printElement<T: CustomStringConvertible>(_ element: T) {
    print(element.description)
}

// opaque 타입 사용
func printElement(_ element: some CustomStringConvertible) {
    print(element.description)
}
  • 기존 제네릭 함수에 명시적으로 T 타입을 선언하던 부분을 프로토콜로 대체

  • 기존에 함수 작성하던 것처럼 간단하게 구현 가능

⚠️ 반환 되는 타입은 항상 같은 구체 타입 일 것

func foo(flag: Bool) -> some Shape {
  if flag {
    return Circle()
  } else {
    return Rectangle() // ❌ 오류
  }
}
  • 컴파일러가 항상 동일한 타입을 반환한다고 보장되어야 함.

some으로 얻게 되는 또다른 이점

https://velog.io/@js1436kt/Swift-Existential-Container

  • 기존 컴파일러가 실제 구현될 타입을 모르기 때문에 런타임에 Existential-Container에 접근하는 일련의 과정으로 발생하는 오버헤드가 발생

  • some 키워드를 사용해 컴파일 타임에 구현될 타입을 알게 되고 정적 디스패치가 가능하게 하여 성능 향상 가능

any

실존 타입(Existential Type)으로 타입을 명시적으로 선언할 때 사용되며 일반 프로토콜 타입 사용을 명확하게 표현하는 프로토콜

왜 사용할까?

any는 기존에 타입을 명시하던 것과 동일한 역할을 함.
ex)

func justFunc() -> Type {
	...
}

func anyFunc() -> any Type {
	...
}
둘 다 동일함.

1. 성능 오버헤드 인지

  • any를 사용하면 existential(boxing)이라는 런타임 동작이 발생하며, 이는 포인터 간접 참조와 동적 디스패치를 수반

  • 성능 관점에서 해당 부분이 문제가 될 수도 있다는 주의를 준다는 느낌.

existential(boxing)

  • any Protocol 타입은 실제 값을 existential container라는 박스로 감싸 저장

  • 이 컨테이너는 최대 3개의 워드를 인라인으로 저장하지만, 이보다 큰 값은 힙에 할당

  • 힙 할당과 해제 과정을 거치므로, 메모리 오버헤드와 ARC 레퍼런스 카운팅이 추가

2. Swift 6에서의 의무화

  • Swift 6에서는 모든 프로토콜 타입 선언에서 any를 붙이는 것이 문법 요구 사항

그러면 some을 쓴 것과 안 쓴 것으로 분류하면 되는 거 아닌가?

1. Swift 개발자가 명시적 표현하기를 원함.

https://github.com/hborla/swift-evolution/blob/existential-any/proposals/NNNN-existential-any.md

  • any 타입을 통하여 "이건 런타임에 타입이 바뀔 여지가 있는 타입이다"라고 코드 수준에서 명확하게 표시하면 좋겠다고 강조되어 있음.

2. 의도 - 비의도 혼동 방지

var delegate: Protocol
- 런타임에 타입이 바뀌는 애인가?
- 고정된 타입만 나오나?

var anyDelegate: any Protocol
- 런타임에 타입이 바뀌어요!!!!!!!!!!!
  • any를 활용해 명시적으로 런타임에 오버헤드가 있다는 것을 명시할 수 있게 됨.

  • 기존에 혼동할 수 있던 부분을 명확히 잡아 줄 수 있음.

정리

구분some Protocolany Protocol
타입 고정성컴파일 시 단일 타입 고정런타임에 여러 타입 허용
디스패치 방식정적 디스패치동적 디스패치
성능빠르고 최적화 가능약간의 오버헤드 존재
사용 위치반환형, 파라미터, 프로퍼티주로 파라미터, 프로퍼티, heterogeneous 컬렉션
유연성구현은 숨기고 형식은 고정다양한 타입을 자유롭게 담을 수 있음

참고
https://www.youtube.com/watch?v=0u4w6FaqhMs

https://www.avanderlee.com/swift/some-opaque-types

https://nsvasilev.medium.com/understanding-any-and-some-keywords-in-swift-462cb42cc913

https://forums.swift.org/t/relative-performance-of-existential-any/77299

https://www.donnywals.com/what-is-the-any-keyword-in-swift

https://stackoverflow.com/questions/76311790/when-do-you-need-to-write-any-when-using-a-protocol-as-a-type-in-swift-5-7

https://github.com/hborla/swift-evolution/blob/existential-any/proposals/NNNN-existential-any.md

https://medium.com/%40fenominall/a-guide-to-some-and-any-keywords-in-swift-comparison-with-generics-and-impact-on-optimization-054c74dd0174

https://namitgupta.com/difference-between-some-and-any-with-protocols-in-swift

profile
안녕하세요! iOS 개발자입니다!

0개의 댓글