MINRYUL
류링류링
MINRYUL
전체 방문자
오늘
어제
  • 분류 전체보기
    • Swift
      • 학습
    • iOS
      • Toy-Project
      • 학습
      • Metal
    • CS
    • TIL

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • RxCocoa
  • configuration_bulid_dir
  • collectionView
  • Protocol
  • Existential any
  • METAL
  • RxTest
  • Swift
  • opaque type
  • WWDC
  • demangle
  • ViewStore
  • some
  • CollectionView Cell
  • Any
  • TDD
  • dynamic frameworkm
  • AttributeText
  • Custom Calendar
  • tuist
  • BDD
  • Clean Architecture
  • WWDC 2024
  • static framework
  • Existential type
  • urlsession
  • RxSwift
  • ios
  • RxNimble
  • TableView Cell

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
MINRYUL

류링류링

Swift Opaque Types(번역)
Swift/학습

Swift Opaque Types(번역)

2023. 3. 28. 14:05

원문: Some keyword in Swift: Opaque types explained with code examples

 

Some keyword in Swift: Opaque types explained with code examples

The some keyword in Swift allows you to define opaque parameter and return types. Expose only minimum APIs without losing performance.

www.avanderlee.com

 

Opaque Type

Concrete Type을 정의하지 않고 예상되는 반환 타입을 설명할 수 있다. 이 같은 것은 SwiftUI body에 사용되는 것을 볼 수 있다.

var body: some View { ... }

 이는 protocol을 리턴하는 것처럼 보일 수 있다. 하지만, some 키워드는 컴파일러가 실제 타입 정보에 접근하고, 최적화를 수행하게 한다. 예를 들면 이러한 타입들이 있다.

var body: VStack { ... }

// or:

var body: Text { ... }

 전체 view hierarchy는 opaque이므로, 컴파일러는 반환된 view body의 정확한 사이즈를 알 수 있다. 컴파일러는 코드를 최적화 할 수 있으므로, 힙의 할당 수가 줄어들게 된다. 세부 사항에 자세히 들어가는 것 없이, 컴파일러에게 some 키워드를 추가함으로 View protocol 만이 반환될 것이라고 하는 것보다 많은 정보를 제공한다고 할 수 있다.

 

일치하는 underlying types이 없는 Opaque 반환 타입

(SwiftUI에서 이러한 오류가 발생할 수 있음)

함수가 opaque 반환 타입인 ‘some View’를 선언했지만, body의 반환문에는 일치하는 underlying types이 없다.

func makeFooterView(isPro: Bool) -> some View {
    if isPro {
        return Text("Hi there, PRO!") // Return type is Text        
    } else {
        return VStack { // Return type is VStack<TupleView<(Text, Button<Text>)>>
            Text("How about becoming PRO?")
            Button("Become PRO", action: {
                // ..
            })
        }
    }
}

위 코드는 view의 VStack, Text 두가지 타입을 반환한다.

 하지만 앞에서 설명했듯이 컴파일러는 some 키워드를 통해 기본 concrete tpye을 반환하고자 한다. value의 scope에 대해 Opaque types가 고정되야 하므로, 같은 method scope내에 다른 types를 반환할 수 없다.

이는 VStack과 같은 wrapping container를 사용해서 위의 코드를 해결할 수 있다.

func makeFooterView(isPro: Bool) -> some View {
    return VStack {
        if isPro {
            Text("Hi there, PRO!")
        } else {
            Text("How about becoming PRO?")
            Button("Become PRO", action: {
                // ..
            })
        }
    }
}

 그러나 isPro가 true인 경우에만 필요한 container를 추가하고 있다. 따라서 @ViewBuilder attribute를 사용해서 위의 코드를 다시 작성하는 것이 좋으며, result builders 대신 사용할 수 있다.

@ViewBuilder
func makeFooterView(isPro: Bool) -> some View {
    if isPro {
        Text("Hi there, PRO!")
    } else {
        VStack {
            Text("How about becoming PRO?")
            Button("Become PRO", action: {
                // ..
            })
        }
    }
}

 

Opaque types를 사용해서 tpye information 숨기기

 some 키워드를 사용하면 지원하는 protocol을 제공하여 return value를 설명할 수 있으므로, 그에 따른 concrete type을 숨길 수 있다. modules를 개발할 때, opaque types를 사용해서 implementors에게 노출을 원하지 않는 concrete types를 숨길 수 있다.

예를 들어, 이미지를 fetching하기 위한 package를 제공하는 경우, image fetcher를 정의할 수 있다.

struct RemoteImageFetcher {
    // ...
}

image fetcher factory를 통해 image fetcher를 제공할 수 있다.

public struct ImageFetcherFactory {
    public func imageFetcher(for url: URL) -> RemoteImageFetcher
}

 API는 Swift package module 내에서 정의되며, implementors에게 API를 노출하려면, public 키워드가 필요하다. concrete “RemoteImageFetcher” return type을 정의했으므로 컴파일러는 remote image fetch를 publicly으로 접근 가능한 코드로 변환이 필요하다.

  • some 키워드를 추가하여 opaque type을 사용하지 않으면, 의도한 것 보다 더 많은 코드를 노출해야 할 수있다.

public “ImageFetching” protocol을 정의해서 반환 타입으로 사용하다면 이 같은 문제를 해결할 수 있다.

public protocol ImageFetching {
    func fetchImage() -> UIImage
}

struct RemoteImageFetcher: ImageFetching {
    func fetchImage() -> UIImage {
        // ...
    }
}

public struct ImageFetcherFactory {
    public func imageFetcher(for url: URL) -> ImageFetching {
        // ...
    }
}

 associated types이 없는 protocol을 반환하면, opaque types을 정의하지 않아도 괜찮지만, associated type인 “Image”를 정의하면 오류가 나게 된다.

public protocol ImageFetching {
    associatedtype Image
    func fetchImage() -> Image
}

public struct ImageFetcherFactory {
    public func imageFetcher(for url: URL) -> ImageFetching {
        // ...
    }
}

 protocol “ImageFatching” 은 Self 나 associated type을 가지고 있으므로 generic constraint로만 사용될 수 있다.

이러한 오류를 해결하려면 opaque types를 정의해야 한다.

 

Solving Protocol(?)은 generic constraint errors로만 사용할 수 있다.

Swift에서 associated type이나 protocol으로 작업할 때 다음 오류가 나는 것이 일반적.

Protocol “X”에는 Self 나 associated type를 가지고 있으므로 generic constraint로만 사용될 수 있다.

 Protocol에는 generic constraints에 대한 정보를 제공해야 한다. 컴파일러는 추가적인 세부 사항 없이 generic constraintes를 해결 할 수 없다. 컴파일러는 some 키워드를 사용해서 추가적인 정보를 읽을 수 있다.

public func imageFetcher(for url: URL) -> some ImageFetching { ... }

 컴파일러가 필요한 모든 정보를 읽을 수 있도록 할 것이지만, concrete “RemoteImageFetcher”와 같은 구현 세부 사항은 숨길 수 있다.

 opaque return type을 사용하면, 위의 예에서 generics constraints를 해결할 수 있지만 protocol을 function parameter로 사용할 때 동일한 오류가 발생 가능하다.

public extension UIImageView {
    // Protocol 'ImageFetching' can only be used as a generic constraint because it has Self or associated type requirements
    func configureImage(with imageFetcher: ImageFetching) {

        // Member 'fetchImage' cannot be used on value of protocol type 'ImageFetching'; use a generic constraint instead
        image = imageFetcher.fetchImage()
    }
}

 위의 오류는 Xcode13에서만 발생한다. Xcode14는 Swift 5.7과 opaque 및 existnetial type에 대한 몇가지 개선사항이 포함된다. SE-0341 Opaque Parameter Declarations 는 구현된 proposals중 하나이고, parameter 선언에서 opaque types이 사용 가능하다. 하지만, 컴파일러는 다음과 같이 말할 것.

protocol “ImageFetching”를 사용하려면 “any ImageFetching”으로 작성해야 한다. Use of protocol ‘ImageFetching’ as a type must be written ‘any ImageFetching’

우리는 any, some 사용가능하다. 따라서 다음과 같이 method를 변경할 수 있다.

func configureImage(with imageFetcher: some ImageFetching)

 

Primary Associated Types와 constraints using some 사용

 다음과 같이 method를 정의하면 컴파일러 오류를 해결가능하지만, image fetching와 관련된 다음과 같은 오류가 발생한다.

public extension UIImageView {
    func configureImage(with imageFetcher: some ImageFetching) {
        // Cannot assign value of type '<anonymous>.Image' to type 'UIImage'
        image = imageFetcher.fetchImage()
    }
}

 컴파일러는 “UIImage”에 value를 강제 언래핑하길 추천하지만, runtime exceptions에 대해 위험하게 된다. 그러므로, SE-358 Primary Associated Types in the Standard Library로 구현된 Swift 5.7의 기능을 통해 Image Facher의 primary associated type을 구성할 수 있다.

public protocol ImageFetching<Image> {
    associatedtype Image
    func fetchImage() -> Image
}

Protocol name 선언 안에 “Image” associated type을 일치시킴으로, image fetching protocol에 대한 primary associated type을 구성한다. Swift 5.7은 또한, proposal SE-0346 Lightweight same-type requirements for primary associated types을 구현함으로, extension method를 “UIImage” types만을 제한하도록 업데이트할 수 있다.

public extension UIImageView {
    func configureImage(with imageFetcher: some ImageFetching<UIImage>) {
        image = imageFetcher.fetchImage()
    }
}

 모든 컴파일러 오류는 해결되었고, 컴파일러가 UIImage를 반환하는 some Image fetcher를 처리하는 것을 알 수 있도록 methed를 구성했다. UIImageView는 해당 image property에 대해 동일한 type이 필요하고, 이에 따라 fetched image를 구성할 수 있다.

 위의 예에서 opaque types를 사용함으로, 코드를 publically하게 노출할 필요가 없어졌고, 이를 통해 변경 사항을 공개하지 않고 내부적으로 코드를 refactor할 수 있게 되었다. 이러한 유연성을 확보하는 것은 일반적인 개발할때 내부 API를 사용하거나 프레임워크를 제공할 경우 매우 중요하다.

 

Replacing generics with some

 some 키워드는 generics을 대체하고, 가독성을 향상시키기 위한 syntactic sugar(문법적 설탕?)으로 사용될 수 있다. generic paramter가 single place에서만 사용되는 경우 opaque type로 대체 가능하다.

예를 들어, custom print method를 정의할 수 있다.

func printElement<T: CustomStringConvertible>(_ element: T) {
    print(element)
}

generic parameter는 single place에서만 사용되므로 다음과 같이 some 키워드로 대체 가능하다.

func printElement(_ element: some CustomStringConvertible) {
    print(element.description)
}

즉, “T where T: Protocol”의 약자로 opaque type인 “some Protocol”을 사용해서 코드의 가독성을 개선할 수 있다.

 

Conclusion

 Swift의 opaque type은 코드를 단순화하고, 가독성을 향상시키는데 도움을 준다. Swift 5.7은 더 많은 곳에서 some 키워드를 활용할 수 있도록 많은 개선사항을 도입했다. primary associated types와 opaque type constraints를 사용해서 강력한 APIs를 만들 수 있게 되었다. 또한, 컴파일러는 concrete types를 숨기는 것을 유지하면서, 코드를 최적화 할 수 있게 되었다.

'Swift > 학습' 카테고리의 다른 글

2022 WWDC Design protocol interfaces in Swift  (0) 2023.05.03
Existential any in Swift explained with code examples(번역)  (0) 2023.04.06
enum으로 특정 단위를 명확하게 표현하기  (0) 2022.09.25
2022 WWDC Embrace Swift Generics  (1) 2022.07.04
Swift Subscripts  (0) 2022.05.23
    'Swift/학습' 카테고리의 다른 글
    • 2022 WWDC Design protocol interfaces in Swift
    • Existential any in Swift explained with code examples(번역)
    • enum으로 특정 단위를 명확하게 표현하기
    • 2022 WWDC Embrace Swift Generics
    MINRYUL
    MINRYUL
    열심히 살자

    티스토리툴바