爲何SwiftUI的返回類型?some View
爲何不能只返回常規協議? 什麼是不透明類型?html
不透明返回類型是Swift 5.1中添加的一項功能,它是新SwiftUI框架功能的重要組成部分。它最終解決了協議使用和Swift API設計的基本問題,爲建立和使用公共API開闢了新的可能性。面試
##在Swift 5.1以前構建API 要了解不透明類型是什麼,讓咱們看看咱們在構建公共API時有什麼可能性。swift
假設咱們有一個支付框架,其中有一個返回用戶最喜歡的信用卡的方法,它是CreditCard struct
:安全
public func favoriteCreditCard() -> CreditCard {
return getLastUsedCreditCard()
}
複製代碼
這對內部API來講很好,但對於公共框架,這可能並不理想。用戶可能不須要訪問CreditCard
類型自己 - 它可能包含咱們不但願用戶玩的信息,例如如何執行散列。bash
您能夠經過仔細選擇哪些方法是公共的以及哪些方法是私有的來解決這些問題,但若是您想徹底隱藏這些類型的存在會怎麼樣?框架
今天,您能夠經過使用協議實現此目的,將實現和類型細節抽象爲統一名稱:函數
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
,您根本沒法執行此操做:ui
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屏幕返回-你須要一個具體的對象,爲您可以比較,進程並在屏幕上的位置,但在大多數狀況下,它並不重要什麼視圖真的是,咱們只須要知道,它是一個。