![](/img/trans.png)
[英]Swift Protocol extension: cannot assign to property: '' is a get-only property
[英]Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?
為什么下面的代碼會產生錯誤?
protocol ProtocolA {
var someProperty: ProtocolB { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA { // Type 'SomeClass' does not conform to protocol 'ProtocolA'
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
這個類似問題的答案是有道理的。 但是,在我的示例中,該屬性是 get-only。 為什么不應該這樣做? 是 Swift 的缺點,還是有什么理由說得通?
沒有真正的理由為什么這不應該是不可能的,只讀屬性要求可以是協變的,因為從類型為ProtocolB
的屬性返回ConformsToB
實例是完全合法的。
Swift 目前不支持它。 為此,編譯器必須在協議見證表和符合要求的實現之間生成一個 thunk ,以便執行必要的類型轉換。 例如,一個ConformsToB
實例需要被裝箱到一個存在容器中才能被輸入為ProtocolB
(並且調用者無法做到這一點,因為它可能對被調用的實現一無所知)。
但同樣,編譯器沒有理由不能這樣做。 有多個錯誤報告就此打開,這個是特定於只讀屬性要求的,還有這個通用的,其中 Swift 團隊的成員 Slava Pestov 說:
[...] 在允許函數轉換的每種情況下,我們都需要協議見證和方法覆蓋
所以它看起來肯定是 Swift 團隊希望在該語言的未來版本中實現的東西。
然而,與此同時,正如@BallpointBen 所說,一種解決方法是使用associatedtype
:
protocol ProtocolA {
// allow the conforming type to satisfy this with a concrete type
// that conforms to ProtocolB.
associatedtype SomeProperty : ProtocolB
var someProperty: SomeProperty { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// implicitly satisfy the associatedtype with ConformsToB.
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
但這很不令人滿意,因為這意味着ProtocolA
不再可用作類型(因為它具有associatedtype
類型要求)。 它還改變了協議所說的內容。 最初它說someProperty
可以返回任何符合ProtocolB
東西——現在它說someProperty
的實現只處理一種符合ProtocolB
特定具體類型。
另一種解決方法是定義一個虛擬屬性以滿足協議要求:
protocol ProtocolA {
var someProperty: ProtocolB { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// dummy property to satisfy protocol conformance.
var someProperty: ProtocolB {
return actualSomeProperty
}
// the *actual* implementation of someProperty.
var actualSomeProperty: ConformsToB
init(someProperty: ConformsToB) {
self.actualSomeProperty = someProperty
}
}
在這里,我們實際上是在為編譯器編寫 thunk——但它也不是特別好,因為它為 API 添加了一個不必要的屬性。
除了 Harmish 的出色回應之外,如果您想在SomeClass
和ProtocolA
上繼續使用相同的屬性名稱,您還可以這樣做
protocol ProtocolB {}
protocol ProtocolA {
var _someProperty_protocolA: ProtocolB { get }
}
extension ProtocolA {
var someProperty: ProtocolB {
return _someProperty_protocolA
}
}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// the *actual* implementation of someProperty.
var _someProperty: ConformsToB
var someProperty: ConformsToB {
// You can't expose someProperty directly as
// (SomeClass() as ProtocolA).someProperty would
// point to the getter in ProtocolA and loop
return _someProperty
}
// dummy property to satisfy protocol conformance.
var _someProperty_protocolA: ProtocolB {
return someProperty
}
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
let foo = SomeClass(someProperty: ConformsToB())
// foo.someProperty is a ConformsToB
// (foo as ProtocolA).someProperty is a ProtocolB
當您符合另一個最初也對someProperty
也有約束的協議ProtocolA2
,這可能很有用,或者當您想隱藏對 swift 限制的 hack 時。
我現在很想知道為什么 Swift 不直接為我做這件事。
從 Swift 5.1 開始,你可以使用不透明的返回類型來引用一個引用另一個協議的協議,只要你也使用關聯類型來這樣做。
它不僅適用於只讀“獲取”屬性,還適用於讀寫屬性。 例如,
protocol ProtocolA {
associatedtype T: ProtocolB
var someProperty: T { get }
var x: Int { get set }
}
protocol ProtocolB {
var x: Int { get set }
}
struct ConformsToB: ProtocolB {
var x: Int
}
class SomeClass: ProtocolA {
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
var x: Int {
get {
someProperty.x
}
set {
someProperty.x = newValue
}
}
}
var protocolA: some ProtocolA = SomeClass(someProperty: ConformsToB(x: 1))
print(protocolA.x) // 1
protocolA.x = 2
print(protocolA.x) // 2
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.