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


96

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

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

セルがaでUITableViewCellありMyProtocol、変数宣言のに準拠していることを指定できますか?

正当化

ファクトリパターンに精通している場合、これは、特定のインターフェイスを実装する特定のクラスのオブジェクトを返すことができるという意味で理にかなっています。

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

提供された型は上記のインターフェースに厳密には準拠していませんが、ファクトリーが返すオブジェクトはそうなので、基本クラスの型と宣言されたプロトコルインターフェースの両方と対話する柔軟性を望んでいます


申し訳ありませんが、これのポイントは迅速に何ですか。タイプは、それらが準拠するプロトコルをすでに知っています。タイプだけを使用しないのですか?
Kirsteins 2014年

1
@Kirsteins型がファクトリから返され、共通の基本クラスを持つジェネリック型である場合を除きます
Daniel Galasko

可能であれば例を挙げてください。
Kirsteins 2014年

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;。このオブジェクトは、NSSomething何に準拠しているかをすでに知っているので、まったく役に立たないようです。のプロトコルのいずれかに準拠していない場合、クラッシュ<>unrecognised selector ...発生します。これはタイプセーフをまったく提供しません。
Kirsteins 2014年

@Kirsteinsもう一度私の例を参照してください。ファクトリがベンドするオブジェクトが、指定されたプロトコルに準拠する特定の基本クラスであることがわかっている場合に使用されます
Daniel Galasko

回答:


72

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

var myVariable: MyClass & MyProtocol & MySecondProtocol

オプションの変数を実行するには:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

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

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

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

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



フィリップありがとうございます!
Omar Albeik

このタイプのオプション変数が必要な場合はどうなりますか?
Vyachaslav Gerchicov

2
@VyachaslavGerchicov:次のように括弧を囲んでから疑問符を付けることができます:var myVariable:(MyClass&MyProtocol&MySecondProtocol)?
フィリップオット

30

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

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()
}

これにより、技術的にcellはと同じですasProtocol

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

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

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

ファクトリーは戻り値の型を指定しているので、オプションのキャストを実行するために技術的に必要ないのですか?プロトコルを明示的に宣言する型付けを実行するために、迅速な暗黙の型付けに頼ることができますか?
Daniel Galasko、2014年

どういう意味かわかりません。英語が下手なのでごめんなさい。について言っている場合-> UITableViewCell<MyProtocol>、これはUITableViewCellジェネリック型ではないため無効です。これでもコンパイルできないと思います。
rintaro 2014年

一般的な実装ではなく、実装の例を示しています。ここでlet asProtocol = ...
Daniel Galaskoが2014年

または、私はただ行うことができます:var cell:protocol <ProtocolOne、ProtocolTwo> = someObject as UITableViewCell and one of the rights in one variable
Daniel Galasko

2
私はそうは思いません。あなたがそうすることができたとしても、cell(コンパイラ用の)プロトコルメソッドしかありません。
rintaro 2014年

2

残念ながら、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を使用すると、プロトコルが付加されたオブジェクトを将来宣言できるようになります。しかし今のところ、これでうまくいきます。

お役に立てれば!


1

編集: 私は間違っていましたが、他の誰かが私のようなこの誤解を読んだ場合、私はそこにこの答えを残します。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の記事「互換性のない型のジェネリック関数」とその他の例も参照してください。

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

私が見逃しているもっと明白な方法はありますか?

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

提供された型は上記のインターフェースに厳密には準拠していませんが、ファクトリーが返すオブジェクトはそうなので、基本クラスの型と宣言されたプロトコルインターフェースの両方と対話する柔軟性を望んでいます

プロトコルと基本クラスの両方のタイプに準拠する汎用メソッド、変数が必要な場合は、うまくいかない可能性があります。しかし、必要な適合メソッドを持つのに十分広いプロトコルを定義する必要があるように思えると同時に、あまり作業をせずに基本クラスにそれを採用するオプションを持つように狭くする必要があります(つまり、クラスがプロトコル)。


1
それは私が話していることではありませんが、感謝します:)クラスと特定のプロトコルの両方を介してオブジェクトとインターフェイスできるようにしたかったのです。obj-cでNSObject <MyProtocol> obj = ...を実行する方法と同じように、これを迅速に実行できないことは言うまでもなく、オブジェクトをそのプロトコルにキャストする必要があります
Daniel Galasko

0

ストーリーボードで汎用インタラクター接続をリンクしようとすると、私はかつて同様の状況になりました(IBではコンセントをプロトコルに接続できません。オブジェクトインスタンスのみです)。これは、基本クラスpublic ivarをプライベート計算でマスクすることで回避できましたプロパティ。これは、それ自体が不正な割り当てを防ぐことはできませんが、実行時に、適合しないインスタンスとの不要な相互作用を安全に防止する便利な方法を提供します。(つまり、プロトコルに準拠していないオブジェクトへのデリゲートメソッドの呼び出しを防ぎます。)

例:

@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。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.