web-dev-qa-db-ja.com

Swiftでは、1つ以上のプロトコルに適合する特定の型の変数をどのように宣言できますか?

Swiftでは、次のように宣言することで変数の型を明示的に設定できます。

var object: TYPE_NAME

さらに一歩進んで、複数のプロトコルに準拠する変数を宣言する場合は、protocol宣言を使用できます。

var object: protocol<ProtocolOne,ProtocolTwo>//etc

1つ以上のプロトコルに準拠し、特定の基本クラス型のオブジェクトを宣言したい場合はどうすればよいですか? Objective-Cの同等物は次のようになります。

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

Swiftでは、次のようになります。

var object: TYPE_NAME,ProtocolOne//etc

これにより、プロトコルで定義された追加のインターフェイスだけでなく、ベースタイプの実装にも柔軟に対応できます。

私が行方不明になっている可能性がある別のより明白な方法はありますか?

例として、プロトコルに準拠するセルを返すUITableViewCellファクトリーがあるとします。プロトコルに準拠したセルを返す汎用関数を簡単にセットアップできます。

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

後でタイプとプロトコルの両方を活用しながら、これらのセルをデキューしたい

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

テーブルビューセルがプロトコルに準拠していないため、これはエラーを返します...

セルがUITableViewCellであり、変数宣言のMyProtocolに準拠することを指定できるようにしたいのですが。

正当化

Factory Pattern に精通している場合、これは特定のインターフェースを実装する特定のクラスのオブジェクトを返すことができるというコンテキストで意味があります。

私の例のように、特定のオブジェクトに適用したときに意味のあるインターフェイスを定義したい場合があります。テーブルビューセルの私の例は、そのような正当化の1つです。

提供された型は上記のインターフェースに正確に準拠していませんが、ファクトリーが返すオブジェクトはそうであるため、基本クラス型と宣言されたプロトコルインターフェースの両方とやり取りする柔軟性が欲しいです

89
Daniel Galasko

Swift 4では、型のサブクラスであり、同時に1つ以上のプロトコルを実装する変数を宣言できるようになりました。

var myVariable: MyClass & MyProtocol & MySecondProtocol

またはメソッドのパラメーターとして:

func shakeEm(controls: [UIControl & Shakeable]) {}

AppleはこれをWWDC 2017で セッション402:Swiftの新機能 で発表しました

第二に、クラスとプロトコルの構成についてお話したいと思います。そのため、ここでは、ちょっとしたシェイク効果を与えて自分自身に注意を引くことができるUI要素用に、このシェイク可能なプロトコルを紹介しました。そして、実際にこのシェイク機能を提供するために、UIKitクラスの一部を拡張しました。そして今、私はシンプルに見える何かを書きたいです。シェイク可能なコントロールの束を取り、それらに注意を引くために有効になっているコントロールを振る関数を作成したいだけです。この配列にここでどのタイプを書き込むことができますか?それは実際にイライラし、トリッキーです。そのため、UIコントロールを使用してみました。しかし、このゲームではすべてのUIコントロールが振れるわけではありません。 Shakableを試すこともできますが、すべてのShakableがUIコントロールではありません。そして、実際にSwift 3でこれを表す良い方法はありません。Swift 4は、任意の数のプロトコルでクラスを構成するという概念を導入します。

63
Philipp Otto

次のような変数を宣言することはできません

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

また、関数の戻り値の型を宣言します

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

このように関数パラメーターとして宣言できますが、基本的にはアップキャストです。

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

現在のところ、できることは次のとおりです。

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

これにより、技術的にcellasProtocolと同一です。

ただし、コンパイラに関しては、cellにはUITableViewCellのみのインターフェイスがあり、asProtocolにはプロトコルインターフェイスのみがあります。したがって、UITableViewCellのメソッドを呼び出すには、cell変数を使用する必要があります。プロトコルメソッドを呼び出す場合は、asProtocol変数を使用します。

セルがプロトコルに準拠していることが確実な場合は、if let ... as? ... {}を使用する必要はありません。好む:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>
30
rintaro

残念ながら、Swiftはオブジェクトレベルのプロトコル準拠をサポートしていません。ただし、目的に役立つ可能性があるやや厄介な回避策があります。

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

次に、UIViewControllerの機能を実行する必要がある場合はどこでも、構造体の.viewControllerアスペクトにアクセスし、プロトコルのアスペクトが必要な場合は、.protocolを参照します。

例えば:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

これで、UIViewControllerに関連する何かを行うためにmySpecialViewControllerが必要なときはいつでも、mySpecialViewController.viewControllerを参照するだけで、プロトコル機能を実行する必要があるときはいつでも、mySpecialViewController.protocolを参照します。

Swift 4を使用すると、将来プロトコルが付加されたオブジェクトを宣言できるようになります。しかし、今のところ、これは機能します。

お役に立てれば!

2
Michael Curtis

EDIT:私は間違っていたが、誰かが私のようなこの誤解を読んだ場合、私はこの答えを残すそこに。 OPは、特定のサブクラスのオブジェクトのプロトコル適合性の確認について質問しましたが、これは受け入れられた答えが示すように別の話です。この回答では、基本クラスのプロトコル準拠について説明しています。

間違っているかもしれませんが、UITableCellViewクラスにプロトコル適合性を追加することについて話しているのではありませんか?その場合、プロトコルはオブジェクトではなく基本クラスに拡張されます。 拡張機能を使用したプロトコルの採用の宣言 に関するAppleのドキュメントを参照してください。

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

すでに参照されているSwiftドキュメントに加えて、Nate Cooksの記事 互換性のない型の汎用関数 も参照してください。

これにより、プロトコルで定義された追加のインターフェイスだけでなく、ベースタイプの実装にも柔軟に対応できます。

私が行方不明になっている可能性がある別のより明白な方法はありますか?

プロトコルの採用はまさにこれを行い、オブジェクトを特定のプロトコルに準拠させます。ただし、特定のプロトコルタイプの変数は、プロトコル以外の情報をnot認識しないというマイナス面に注意してください。しかし、これは、必要なすべてのメソッド/変数/を持つプロトコルを定義することで回避できます...

提供された型は上記のインターフェースに正確に準拠していませんが、ファクトリーが返すオブジェクトはそうであるため、基本クラス型と宣言されたプロトコルインターフェースの両方とやり取りする柔軟性が欲しいです

汎用メソッド、変数をプロトコルと基本クラスの両方のタイプに適合させたい場合は、運が悪いかもしれません。しかし、必要な適合メソッドを得るのに十分な幅のプロトコルを定義すると同時に、あまり多くの作業をせずにベースクラスにそれを採用するオプションを持つのに十分に狭い(つまり、クラスがプロトコル)。

1
holroy

ストーリーボードでジェネリックインタラクター接続をリンクしようとしたときに同様の状況に陥ったことがあります(IBでは、アウトレットをプロトコルに接続することはできません。オブジェクトインスタンスのみ)。プロパティ。これにより、誰かが違法な割り当てを行うこと自体を防ぐことはできませんが、実行時の不適合インスタンスとの望ましくない相互作用を安全に防ぐ便利な方法を提供します。 (つまり、プロトコルに準拠していないオブジェクトへのデリゲートメソッドの呼び出しを禁止します。)

例:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

「outputReceiver」は、プライベート「protocolOutputReceiver」と同様にオプションとして宣言されます。常に後者(計算されたプロパティ)を介してoutputReceiver(別名デリゲート)にアクセスすることにより、プロトコルに準拠していないオブジェクトを効果的にフィルターで除外します。オプションのチェーンを使用して、プロトコルを実装するかどうかにかかわらず、デリゲートオブジェクトを安全に呼び出すことができます。

これを状況に適用するには、パブリックivarのタイプを「YourBaseClass?」にすることができます。 (AnyObjectとは対照的に)、プライベートな計算プロパティを使用して、プロトコルの適合性を強制します。 FWIW。

0
quickthyme