簡體   English   中英

為什么協議中的 get-only 屬性要求不能被符合的屬性滿足?

[英]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 的出色回應之外,如果您想在SomeClassProtocolA上繼續使用相同的屬性名稱,您還可以這樣做

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM