美文网首页工作生活
Understanding Opaque Return Type

Understanding Opaque Return Type

作者: simpletonking | 来源:发表于2019-07-02 11:49 被阅读0次

    本文 翻译自 https://swiftrocks.com/understanding-opaque-return-types-in-swift.html

    Understanding Opaque Return Types in Swift

    为什么 SwiftUI 的返回类型 是 some View?

    为什么 不能返回一个 普通的 协议?

    Opaque Types 是什么?

    Opaque Types 是一个 在 Swift 5.1 新添加的特性,它是很大一部分新的SwiftUI框架的功能。它解决了协议的使用问题和给Swift API的设计 提供了 新的使用 public APIs的 创建和使用可能,

    在 Swift 5.1 之前构建APIs

    为了去明白 Opaque Types 是什么 ,让我们先看看现在构建 public APs 的可行方案。

    让我们 首先假设我们 有一个 payment(支付)的框架,它有一个 返回用户最喜欢的 信用卡的方法 ,返回值是 CreditCard(信用卡) struct。

    
    public func favoriteCreditCard() -> CreditCard {
    
        return getLastUsedCreditCard()
    
    }
    
    

    这个方法对于内部的 API 来说非常棒,当对于一个 public 的 framework 来说就不是一个好主意了。用户可能没必要去获取到 CreditCard 这个类型。它可能包含的一些信息是我们不想让用户能够去交流的信息,像 hashing 方法如何去执行。

    你也可以通过设置方法为public和private来解决。但是如果你想完全隐藏掉这个类型的存在呢?

    现在,你可以通过协议来完成这个目标,通过将实现和类型细节抽象进入 protocol 中,用一个统一的协议名字来代替这个类型。

    protocol PaymentType { /* ... */ }
    struct CreditCard: PaymentType { /* ... */ }
    
    public func favoriteCreditCard() -> PaymentType {
        return getLastUsedCreditCard() // () -> CreditCard
    }
    

    有了这个协议,我们甚至可以重写 favoriteCreditCard() 为泛型方法,它返回了不是 Credit cards type的 Payment 类型。

    struct ApplePay: PaymentType { /* ... */ }
    
    func favoritePaymentType() -> PaymentType {
        if likesApplePay {
            return ApplePay()
        } else {
            return getLastUsedCreditCard()
        }
    }
    

    不幸的是,这个协议的使用会产生一个大问题。因为 Swift 的协议会丢弃类型的基本标识,如果一个协议如果有 关联类型(包括 Self )的要求,比如 从 Equatable 继承的协议,那么它将不能像下面这么做。

    protocol PaymentType: Equatable { /* ... */ }
    
    public func favoriteCreditCard() -> PaymentType {
        return getLastUsedCreditCard() // () -> CreditCard
    }
    // Error: Protocol 'PaymentType' can only be used as a generic constraint because it has Self or associated type requirements
    

    这意味着说 api 的使用者将不能比较两个 payment 类型,即使他们的底层类型是相同的

    let creditCard = favoriteCreditCard()
    let anotherCreditCard = mostRecentCreditCard()
    
    creditCard == anotherCreditCard // `PaymentType` does not conform to Equatable.
    

    在 Swift5.1 之前,这个问题的解决方法是通过特别的泛型方式来解决,将所有信息都放入一个类中或者使用类型擦除技术。以上这些方法的使用都会使 API 的使用更加复杂,并且会带来不同类型的问题到 app 中。例如,考量下下面这个方法:

    func getHashedCard() -> HashedObject<CreditCard>
    

    泛型的使用可以解决上面所提到的协议问题,但是它们同时也使API更难处理对待。可能使用HashedObject 在内部是非常重要的,但是使用者更可能不需要知道这些,返回一个PaymentType object 类型对象代替可能会更好,但是协议的局限性又阻止了这样的可能性。

    Opaque Return Types

    这个问题在 Swift5.1 的 Opaque Return Types 到来后又个明确的解决方法了。如果你有一个方法返回了一个由 protocol 掩盖的具体实体类型——就像上文所使用的实体 CreditCard type 由
    不是很有用的 PaymentType 所掩盖的,你可以通过将返回类型改成 some {type name} 来使用
    Opaque Return Types 。

    public func favoriteCreditCard() -> some PaymentType {
        return getLastUsedCreditCard() // () -> CreditCard
    }
    

    当这么做了后,返回的实际类型将会是确切的实际类型 CreditCard ,但是编译器将会假装用协议来代替它。这意味着当使用者看到这个通常的协议,它将有所有这个实体类型的能力:

    let creditCard = favoriteCreditCard() // some 'PaymentType'
    let debitCard = mostRecentCreditCard() // some 'PaymentType'
    

    creditCard == debitCard // Now works, because two concrete CreditCards can be compared.

    这个可以成功运行的理由是因为用了很具有想象力的编译器魔法——返回值一直是 CreditCard,它仅仅在你的编码层面被隐藏了。下面这个是 favoriteCreditCard() 被后的情况:

    let favoriteCreditCardMangledName = "$s3MyApp9favoriteCreditCardQryF"
    public func favoriteCreditCard() -> @_opaqueReturnTypeOf(favoriteCreditCardMangledName, 0) {
        return getLastUsedCreditCard() // () -> CreditCard
    } like after compiling:
    
    

    所有 favoriteCreditCard() 返回的 some PaymentType 的引用都被内部属性所代替-- 在这个内部属性执行过程中,将会带上标识并且用它来提供确切类型, 即 CreditCard ,它将会储存在 方法的 AST(抽象语法树)元数据中。

    // The definition of favoriteCreditCard() contains:
    (opaque_result_decl
      (opaque_type interface type='(some PaymentType).Type' naming_decl="favoritePaymentType()" underlying:
        substitution τ_0_0 -> CreditCard)))
    

    因此,虽然在 IDE 中,你将会被阻止获取特殊的 CreditCard 的属性,但是在运行时,下面这个

    public func favoriteCreditCard() -> some PaymentType {
        return getLastUsedCreditCard() // () -> CreditCard
    }
    

    与直接返回CreditCard相同。

    public func favoriteCreditCard() -> CreditCard {
        return getLastUsedCreditCard() // () -> CreditCard
    }
    

    为什么它是有用的呢?

    Opaque Return Types 是一个 给了api的使用者一个不需要暴露这些返回类型的实际类型,却又能获得它们的能力的类型。 某些时候,知道协议的基础类型是不必要的,但是你需要这些类型的能力去继续运行。PaymentType 这个例子对于 Opaque Return Types 来说可能太简单了,因此我们来看看在几个内部辅助类型上面是如何应用的,例如 lazy funcitons:

    let lazyMap = [1,2,3].map { $0 * 2 }
    let lazyFilter = lazyMap.filter { $0.isMultiple(of: 2) }
    let lazyDrop = lazyFilter.drop { $0 != 2 }
    

    lazyMap的类型是 LazyMapSequence<[Int], Int>,
    lazyFilter的类型是LazyFilterSequence<LazyMapSequence<[Int], Int>>,
    lazyDrop的类型是 LazyDropWhileSequence<LazyFilterSequence<LazyMapSequence<[Int], Int>>>

    创建一个基于 Sequence protocol 的方法将会避免方法的使用者使用这个类型所具有的功能,但是返回一个如此巨大的特殊的泛型类型将是很疯狂的。
    使用者很可能不在意是什么内部的辅助类型组成了这个对象。有了 opaque return types,你可以作为一个普通的 Sequence 类型安全的返回它,同时仍然保持原始类型的能力。

    func getLazyDrop() -> some Sequence {
        let lazyMap = [1,2,3].lazy.map { $0 * 2 }
        let lazyFilter = lazyMap.filter { $0.isMultiple(of: 2) }
        let lazyDrop = lazyFilter.drop { $0 != 2 }
        return lazyDrop
    }
    

    这就是 为什么 SwiftUI 返回的是 some View—— 你需要实际的对象去比较,处理和在屏幕上定位它们,但是在大多数例子下,这个 View 到底是什么并不重要,我们只需要知道它是其中之一。最后,这就是一个工具,它能让你的编码更轻松。

    转载请注明来源

    相关文章

      网友评论

        本文标题:Understanding Opaque Return Type

        本文链接:https://www.haomeiwen.com/subject/ivegcctx.html