簡體   English   中英

使 swift 協議符合 Hashable

[英]Make a swift protocol conform to Hashable

我正在Hashable試圖讓Hashable與符合相同protocol多個struct一起工作。

我有一個協議SomeLocation聲明如下:

protocol SomeLocation {
    var name:String { get }
    var coordinates:Coordinate { get }
}

然后我創建多個包含類似數據的對象,如下所示:

struct ShopLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate

    init(from decoder: Decoder) throws {
        ...
    }
}

struct CarLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate

    init(from decoder: Decoder) throws {
        ...
    }
}

我稍后可以通過聲明在同一個數組中使用這些:

let locations: [SomeLocation]

問題是,我創建了一個MKAnnotation子類,並且需要在SomeLocation對象上使用自定義Hashable

final class LocationAnnotation:NSObject, MKAnnotation {
    let location:SomeLocation
    init(location:SomeLocation) {
        self.location = location
        super.init()
    }
}

override var hash: Int {
    return location.hashValue
}

override func isEqual(_ object: Any?) -> Bool {
    if let annot = object as? LocationAnnotation
    {
        let isEqual = (annot.location == location)
        return isEqual
    }
    return false
}

這給了我兩個錯誤:

“SomeLocation”類型的值沒有成員“hashValue”二元運算符

“==”不能應用於兩個“SomeLocation”操作數

所以我將Hashable協議添加到我的SomeLocation協議中:

protocol SomeLocation: Hashable {
    ...
}

這消除了 hashValue 不可用的第一個錯誤,但現在我收到一個錯誤,我聲明了let location:SomeLocation

協議 'SomeLocation' 只能用作通用約束,因為它具有 Self 或關聯類型要求

所以看起來我不能將Hashable添加到協議中。

我可以將Hashable直接添加到實現SomeLocation協議的每個結構中,但這意味着我需要使用這樣的代碼並在每次我可能創建另一個符合SomeLocation協議的對象時不斷更新它。

override var hash: Int {
    if let location = location as? ShopLocation
    {
        return location.hashValue
    }
    return self.hashValue
}

我嘗試了另一種方法,通過創建SomeLocationRepresentable結構:

struct SomeLocationRepresentable {
    private let wrapped: SomeLocation
    init<T:SomeLocation>(with:T) {
        wrapped = with
    }
}
extension SomeLocationRepresentable: SomeLocation, Hashable {
    var name: String {
        wrapped.name
    }
    
    var coordinates: Coordinate {
        wrapped.coordinates
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(coordinates)
    }

    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.name == rhs.name && lhs.coordinates == rhs.coordinates
    }
}

但是當我嘗試在LocationAnnotation類中使用它時

let location: SomeLocationRepresentable
init(location:SomeLocation) {
    self.location = SomeLocationRepresentable(with: location)
    super.init()
}

我收到一個錯誤

協議類型“SomeLocation”的值不能符合“SomeLocation”; 只有結構/枚舉/類類型可以符合協議

是否有可能實現我想要做的事情? 使用所有符合協議的對象並使用自定義Hashable來比較一個對象?

Hashable派生協議並使用類型橡皮擦可能會有所幫助:

protocol SomeLocation: Hashable {
    var name: String { get }
    var coordinates: Coordinate { get }
}

struct AnyLocation: SomeLocation {
    let name: String
    let coordinates: Coordinate
    
    init<L: SomeLocation>(_ location: L) {
        name = location.name
        coordinates = location.coordinates
    }
}

然后您可以簡單地在結構上聲明協議一致性,如果Coordinate已經是Hashable ,那么您不需要編寫任何額外的散列代碼代碼,因為編譯器可以自動為您合成(對於新類型也是如此)只要它們的所有屬性都是Hashable

struct ShopLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate
}

struct CarLocation: SomeLocation, Decodable {
    var name: String
    var coordinates: Coordinate
}

如果Coordinate也是Codable ,那么您也可以省略為編碼/解碼操作編寫任何代碼,編譯將合成所需的方法(前提是所有其他屬性都已經是Codable )。

然后,您可以通過轉發初始化器約束在注釋類中使用橡皮擦:

final class LocationAnnotation: NSObject, MKAnnotation {   
    let location: AnyLocation
    
    init<L: SomeLocation>(location: L) {
        self.location = AnyLocation(location)
        super.init()
    }
    
    override var hash: Int {
        location.hashValue
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        (object as? LocationAnnotation)?.location == location
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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