了解Swift中的不透明返回类型

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

为什么不能只返回常规协议?

什么是不透明类型?


不透明返回类型是Swift 5.1中添加的一项功能,它是新SwiftUI框架功能的重要组成部分。它最终解决了协议使用和Swift API设计的基本问题,为创建和使用公共API开辟了新的可能性。


对于RxSwift,有更好的见解,想要更好的探讨,可以进入iOS技术群,一起探讨交流

点击此处进交流群 有技术的来闲聊 没技术的来学习

在Swift 5.1之前构建API

要了解不透明类型是什么,让我们看看我们在构建公共API时有什么可能性。

假设我们有一个支付框架,其中有一个返回用户最喜欢的信用卡的方法,它是`CreditCard struct`:

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

这对内部API来说很好,但对于公共框架,这可能并不理想。用户可能不需要访问 CreditCard 类型本身 - 它可能包含我们不希望用户玩的信息,例如如何执行散列。

您可以通过仔细选择哪些方法是公共的以及哪些方法是私有的来解决这些问题,但如果您想完全隐藏这些类型的存在会怎么样?

今天,您可以通过使用协议实现此目的,将实现和类型细节抽象为统一名称:

protocol PaymentType { /* ... */ }
struct CreditCard: PaymentType { /* ... */ }

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

有了这个,我们甚至可以重写为一种通用方法,可以返回非信用卡的付款方式favoriteCreditCard ()

struct ApplePay: PaymentType { /* ... */ }

func favoritePaymentType() -> PaymentType {
    if likesApplePay {
        return ApplePay()
    } else {
        return getLastUsedCreditCard()
    }
}

不幸的是,这种协议的使用存在一个主要问题。因为Swift协议丢弃了该类型的基础身份,如果您的协议恰好具有相关类型/自我要求,例如继承的类型/自我要求 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的用户永远无法直接比较两种付款类型,即使它们是相同的类型:

let creditCard = favoriteCreditCard()
let anotherCreditCard = mostRecentCreditCard()

creditCard == anotherCreditCard // `PaymentType` does not conform to Equatable.

在Swift 5.1之前,解决方案是破解泛型,将所有内容转换为类或使用类型擦除技术,所有这些都会使API的使用更加困难,并将不同类型的问题带入应用程序。例如,考虑这种方法:

func getHashedCard() -> HashedObject<CreditCard>

泛型的使用可以解决这个问题,但它们很容易使API难以处理。也许`HashedObject`内部使用很重要,但用户可能不需要知道它 - 如果将其作为简单 PaymentType 对象返回会更好,但协议限制会阻止它。

不透明的返回类型

对此的明确解决方案以不透明返回类型的形式到达Swift 5.1 。如果你有一个方法返回一个被掩盖为协议的具体类型 - 就像我们的示例返回一个被掩盖为一个不太有用的协议的具体类型,你可以通过将返回类型改为:来使用不透明返回类型:favoriteCreditCard() CreditCardPaymentType some {type name}

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

完成此操作后,方法的返回类型将成为实际的CreditCard具体类型,但编译器将假装它是协议。这意味着虽然API的用户将此视为常规协议,但它将具有具体类型的所有功能:

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
}

到的所有参考文献的返回与内部属性被替换-这在执行期间,将采取的标识符,并且用它来提供实际的返回类型,存储在该方法的AST的元数据:some PaymentType favoriteCreditCard() CreditCard

// 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

}

为什么有用?

不透明返回类型的目的是为API用户提供具体类型的功能,而不必不必要地暴露它。有时,不需要知道协议的基础类型,但您需要继续执行其功能。PaymentType示例可能过于简单,所以让我们看看如何将它应用于具有多个内部帮助器类型的类型,如`lazy`函数:

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> lazyFilterLazyFilterSequence<LazyMapSequence<[Int], Int>> lazyDropLazyDropWhileSequence<LazyFilterSequence<LazyMapSequence<[Int], Int>>>


创建一个返回基本 Sequence 协议的方法将阻止该方法的用户使用完整类型的功能,但创建一个返回这种超特定泛型类型的方法也很疯狂 - 用户可能不关心哪个内部帮助器类型正在撰写这个对象。使用不透明的返回类型,您可以安全地将其作为普通 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屏幕返回-你需要一个具体的对象,为您能够比较,进程并在屏幕上的位置,但在大多数情况下,它并不重要什么视图真的是,我们只需要知道,它是一个。


小编这里有大量的书籍和面试资料哦(点击下载


原文转载地址:Understanding Opaque Return Types in Swift

发布于 2019-08-03 13:37