2017-04-06 12 views
2

私はいくつかの構造体が準拠している基本プロトコル(モデル)を持っています。また、Hashableに準拠していますハッシュ可能な適合性を確認する

protocol Model {} 
struct Contact: Model, Hashable { 
    var hashValue: Int { return ... } 
    static func ==(lhs: Contact, rhs: Contact) -> Bool { return ... } 
} 
struct Address: Model, Hashable { 
    var hashValue: Int { return ... } 
    static func ==(lhs: Address, rhs: Address) -> Bool { return ... } 
} 

私はModel([Model])に準拠したオブジェクトの配列をとる関数を持っています。 Model Hashableを作成せずにHashableを必要とする関数に[Model]を渡すにはどうすればよいですか?私はこの

protocol SomethingElse { 
    var data: [Model] { get } 
} 
+0

'? –

+0

私はそのシナリオを持っていません – joels

+1

多少の関連性:[Equatableを実装している構造体の配列の操作](http://stackoverflow.com/q/41298464/2976878) - 'AnyHashableModel'型の消去されたラッパーをそのQ&Aの 'AnyVehicle'と同様の方法です(主な違いは、単に' hashValue'取得のために余分な関数を格納することです)。 – Hamish

答えて

2

問題を行う際

func complete(with models: [Model]) { 
    doSomethingWithHashable(models) //can't do this 
} 
func doSomethingWithHashable <T:Hashable>(_ objects: [T]) { 
    // 
} 

私は私が得るので、この

protocol Model: Hashable {} 
func complete<T:Model>(with models: [T]) { 
    runComparison(models) 
} 

を避けるためにしようとしている「モデルは...一般的な制約として使用することはできません」あなたのコードでは、Modelという言葉で話していることです。はありません。Hashable適合です。あなたが指摘しているように、これについてコンパイラに伝えること(すなわち、ModelHashableから派生させること)は、Modelに準拠する異種のタイプに関して話す能力を失うことになります。

あなたも、あなただけの完全に任意Hashable準拠したインスタンスのための標準ライブラリのAnyHashable種類消去されたラッパーを使用することができ、最初の場所では約Model適合性を気にしない場合。

しかし、あなたはおよそModel適合性を気にしないと仮定すると、あなたはModelHashableの両方に準拠してインスタンスにtype-erased wrapperあなた自身を構築する必要があります。 my answer hereでは、Equatable適合タイプのためにどのようにタイプ消しゴムを組み立てることができるかを実証します。ロジックは非常に簡単に拡張することができますHashable - 我々はちょうどインスタンスのhashValueを返すために特別な関数を格納する必要があります。

struct AnyHashableModel : Model, Hashable { 

    static func ==(lhs: AnyHashableModel, rhs: AnyHashableModel) -> Bool { 

     // forward to both lhs's and rhs's _isEqual in order to determine equality. 
     // the reason that both must be called is to preserve symmetry for when a 
     // superclass is being compared with a subclass. 
     // if you know you're always working with value types, you can omit one of them. 
     return lhs._isEqual(rhs) || rhs._isEqual(lhs) 
    } 

    private let base: Model 

    private let _isEqual: (_ to: AnyHashableModel) -> Bool 
    private let _hashValue:() -> Int 

    init<T : Model>(_ base: T) where T : Hashable { 

     self.base = base 

     _isEqual = { 
      // attempt to cast the passed instance to the concrete type that 
      // AnyHashableModel was initialised with, returning the result of that 
      // type's == implementation, or false otherwise. 
      if let other = $0.base as? T { 
       return base == other 
      } else { 
       return false 
      } 
     } 

     // simply assign a closure that captures base and returns its hashValue 
     _hashValue = { base.hashValue } 
    } 

    var hashValue: Int { return _hashValue() } 
} 

あなたがそのようにようにそれを使用します:たとえば

私はあなたにもModel年代のラッパーとしてそれを使用したいと思うことを仮定していここ

func complete(with models: [AnyHashableModel]) { 
    doSomethingWithHashable(models) 
} 

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) { 
    // 
} 

let models = [AnyHashableModel(Contact()), AnyHashableModel(Address())] 
complete(with: models) 

要件(いくつかあると仮定)。また、あなたがbaseプロパティを公開し、AnyHashableModel自体からModel適合を削除し、発信者が基礎となるModel準拠したインスタンスのbaseにアクセスすることができます。

struct AnyHashableModel : Hashable { 
    // ... 
    let base: Model 
    // ... 
} 

ただし上記のタイプが消去されたことをラッパーに注意しますHashableModelの両方にのみ適用されます。準拠しているインスタンスがHashableである他のプロトコルについて話したいのですが?

もっと一般的な解決法は、in this Q&Aを実証すると、代わりに、Hashableの両方のタイプを受け入れ、そのタイプは汎用プレースホルダによって表されます。

現在のところ、Swiftには、別の汎用プレースホルダーによって与えられたプロトコルに準拠した汎用プレースホルダーを表現する方法がないため、この関係は、必要なアップキャストを実行するためにtransformクロージャを使用して発信者が定義する必要があります。しかし、Swift 3.1が拡張で具体的な同じタイプの要件を受け入れることで、Modelのこの定型文を削除する便利な初期設定を定義できます(これは他のプロトコルタイプに対しても繰り返すことができます)。例えば

/// Type-erased wrapper for a type that conforms to Hashable, 
/// but inherits from/conforms to a type T that doesn't necessarily require 
/// Hashable conformance. In almost all cases, T should be a protocol type. 
struct AnySpecificHashable<T> : Hashable { 

    static func ==(lhs: AnySpecificHashable, rhs: AnySpecificHashable) -> Bool { 
     return lhs._isEqual(rhs) || rhs._isEqual(lhs) 
    } 

    let base: T 

    private let _isEqual: (_ to: AnySpecificHashable) -> Bool 
    private let _hashValue:() -> Int 

    init<U : Hashable>(_ base: U, upcast: (U) -> T) { 

     self.base = upcast(base) 

     _isEqual = { 
      if let other = $0.base as? U { 
       return base == other 
      } else { 
       return false 
      } 
     } 

     _hashValue = { base.hashValue } 
    } 
    var hashValue: Int { return _hashValue() } 
} 

// extension for convenience initialiser for when T is Model. 
extension AnySpecificHashable where T == Model { 
    init<U : Model>(_ base: U) where U : Hashable { 
     self.init(base, upcast: { $0 }) 
    } 
} 

あなたは今AnySpecificHashable<Model>であなたのインスタンスをラップしたい:

何 `Model`に準拠して、別のタイプがある場合は、ではなく、`ハッシュ可能に
func complete(with models: [AnySpecificHashable<Model>]) { 
    doSomethingWithHashable(models) 
} 

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) { 
    // 
} 

let models: [AnySpecificHashable<Model>] = [ 
    AnySpecificHashable(Contact()), 
    AnySpecificHashable(Address()) 
] 

complete(with: models) 
関連する問題