web-dev-qa-db-ja.com

プロトコルには、SelfまたはrelatedType要件があるため、汎用制約としてのみ使用できます

プロトコルRequestTypeがあり、次のようなrelatedTypeモデルがあります。

public protocol RequestType: class {

    associatedtype Model
    var path: String { get set }

}

public extension RequestType {

    public func executeRequest(completionHandler: Result<Model, NSError> -> Void) {
        request.response(rootKeyPath: rootKeyPath) { [weak self] (response: Response<Model, NSError>) -> Void in
            completionHandler(response.result)
            guard let weakSelf = self else { return }
            if weakSelf.logging { debugPrint(response) }
        }
    }

}

今、私はすべての失敗したリクエストのキューを作成しようとしています。

public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    let queue = [RequestType]()

}

しかし、let queue = [RequestType]()行でエラーが発生します。ProtocolRequestTypeはSelfまたはrelatedTypeの要件があるため、汎用制約としてのみ使用できます。

66
Rahul Katariya

とりあえず、プロトコルを調整して、関連するタイプを使用するルーチンを追加するとします。

 public protocol RequestType: class {
     associatedtype Model
     var path: String { get set }

     func frobulateModel(aModel: Model)
 }

Swiftを使用すると、RequestTypeの配列を自由に作成できます。これらの要求タイプの配列を関数に渡すことができます。

func handleQueueOfRequests(queue: [RequestType]) {
    // frobulate All The Things!

    for request in queue {
       request.frobulateModel(/* What do I put here? */)
    }
}

私はすべてのことをフロブレーションしたいという点に到達しますが、呼び出しに渡す引数のタイプを知る必要があります。私のRequestTypeエンティティの一部はLegoModelを取り、一部はPlasticModelを取り、他はPeanutButterAndPeepsModelを取ります。 Swiftはあいまいさに満足していないため、関連するタイプを持つプロトコルの変数を宣言できません。

同時に、たとえば、すべてがRequestTypeを使用していることがわかっているときに、LegoModelの配列を作成することは完全に理にかなっています。これは理にかなっているように思えますが、それを表現する何らかの方法が必要です。

それを行う1つの方法は、実際の型を抽象モデル型名に関連付けるクラス(または構造体、または列挙型)を作成することです。

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

LegoRequestTypeの配列を宣言することは完全に合理的です。なぜなら、frobulateをすべて使用したい場合は、毎回LegoModelを渡す必要があることがわかっているからです。

関連付けられたタイプのこのニュアンスにより、それらを使用するプロトコルは特別なものになります。 Swift標準ライブラリには、このようなプロトコルで最も注目すべきはCollectionまたはSequenceです。

Collectionプロトコルを実装するものの配列またはシーケンスプロトコルを実装するもののセットを作成できるようにするために、標準ライブラリは「type-erasure」と呼ばれる手法を使用して、構造型AnyCollection<T>またはAnySequence<T>を作成します。型消去の手法はStack Overflowの回答で説明するのはかなり複雑ですが、Webを検索すると、それに関する記事がたくさんあります。

YouTubeの 関連タイプ(PAT)のプロトコルに関するアレックスギャラガー からビデオをお勧めします。

109
Scott Thompson

コードの設計に少し変更を加えると、それが可能になります。プロトコル階層の最上部に、空の、関連付けられていないタイプのプロトコルを追加します。このような...

public protocol RequestTypeBase: class{}

public protocol RequestType: RequestTypeBase {

    associatedtype Model
    var path: Model? { get set } //Make it type of Model

}
public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    var queue = [RequestTypeBase]() //This has to be 'var' not 'let'

}

別の例では、プロトコルRequestTypeから派生したクラスを使用してキューを作成し、適切なタイプを出力するためにキューを関数に渡します

public class RequestA<AType>: RequestType{
   public typealias Model = AType
   public var path: AType?
}
public class RequestB<BType>: RequestType{
   public typealias Model = BType
   public var path: BType?
}

var queue = [RequestTypeBase]()

let aRequest: RequestA = RequestA<String>()
aRequest.path = "xyz://pathA"

queue.append(aRequest)

let bRequest: RequestB = RequestB<String>()
bRequest.path = "xyz://pathB"

queue.append(bRequest)

let bURLRequest: RequestB = RequestB<URL>()
bURLRequest.path = URL(string: "xyz://bURLPath")

queue.append(bURLRequest)

func showFailed(requests: [RequestTypeBase]){

    for request in requests{
        if let request = request as? RequestA<String>{
            print(request.path!)
        }else if let request = request as? RequestB<String>{
            print(request.path!)
        }else if let request = request as? RequestB<URL>{
            print(request.path!)
        }

    }
}

showFailed(requests: queue)
1
Farhan Arshad