配列型および関数パラメーターとしてのプロトコルの迅速な使用


136

特定のプロトコルに準拠したオブジェクトを格納できるクラスを作成したい。オブジェクトは型付き配列に格納する必要があります。Swiftのドキュメントによると、プロトコルはタイプとして使用できます。 

タイプであるため、次のような他のタイプが許可されている多くの場所でプロトコルを使用できます。

  • 関数、メソッド、または初期化子のパラメーター型または戻り型として
  • 定数、変数、またはプロパティのタイプとして
  • 配列、辞書、またはその他のコンテナー内のアイテムのタイプとして

ただし、次の場合はコンパイラエラーが発生します。

プロトコル 'SomeProtocol'は、Selfまたは関連する型の要件があるため、一般的な制約としてのみ使用できます

これをどのように解決しますか?

protocol SomeProtocol: Equatable {
    func bla()
}

class SomeClass {
    
    var protocols = [SomeProtocol]()
    
    func addElement(element: SomeProtocol) {
        self.protocols.append(element)
    }
    
    func removeElement(element: SomeProtocol) {
        if let index = find(self.protocols, element) {
            self.protocols.removeAtIndex(index)
        }
    }
}

2
Swiftには、それを実装する型に対してポリモーフィズムを提供しない特別なクラスのプロトコルがあります。そのようなプロトコルは、その定義でSelfまたはAssociatedTypeを使用します(Equatableはその1つです)。場合によっては、型消去されたラッパーを使用してコレクションを準同型にすることができます。たとえばここを見てください。
ワルダイバー2016

回答:


48

Swiftのプロトコルの問題のバリアントにヒットしましたが、まだ適切な解決策はありません。

Swiftでソートされているかどうかを確認するには、配列の拡張も参照してください、それはあなたの特定の問題に適しているかもしれないそれを回避する方法についての提案を含んでいます(あなたの質問は非常に一般的です、多分あなたはこれらの答えを使って回避策を見つけることができます)。


1
今のところこれが正解だと思います。ネイトの解決策は機能していますが、私の問題を完全には解決しません。
snod 2014

32

次のようにSomeProtocol、使用するクラスがに準拠することを必要とする型制約を持つジェネリッククラスを作成したいとします。

class SomeClass<T: SomeProtocol> {
    typealias ElementType = T
    var protocols = [ElementType]()

    func addElement(element: ElementType) {
        self.protocols.append(element)
    }

    func removeElement(element: ElementType) {
        if let index = find(self.protocols, element) {
            self.protocols.removeAtIndex(index)
        }
    }
}

そのクラスのオブジェクトをどのようにインスタンス化しますか?
snod

シングルタイプを使用してにうーん...この方法では、ロックあなたのものに準拠SomeProtocol-let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
ネイト・クック

この方法では、クラスのオブジェクトのみをMyMemberClass配列に追加できますか?
snod

またはlet foo = SomeClass<MyMemberClass>()
DarkDust 2014

@snodええ、それはあなたが探しているものではありません。問題はEquatable適合です-それなしで正確なコードを使用できます。多分バグ/機能リクエストを提出しますか?
ネイト・クック

15

Swiftには、それを実装する型に対してポリモーフィズムを提供しない特別なクラスのプロトコルがあります。そのようなプロトコルは、その定義で(SelfまたはassociatedtypeそのEquatable1つです)を使用またはキーワードします。

場合によっては、型消去されたラッパーを使用してコレクションを準同型にすることができます。以下に例を示します。

// This protocol doesn't provide polymorphism over the types which implement it.
protocol X: Equatable {
    var x: Int { get }
}

// We can't use such protocols as types, only as generic-constraints.
func ==<T: X>(a: T, b: T) -> Bool {
    return a.x == b.x
}

// A type-erased wrapper can help overcome this limitation in some cases.
struct AnyX {
    private let _x: () -> Int
    var x: Int { return _x() }

    init<T: X>(_ some: T) {
        _x = { some.x }
    }
}

// Usage Example

struct XY: X {
    var x: Int
    var y: Int
}

struct XZ: X {
    var x: Int
    var z: Int
}

let xy = XY(x: 1, y: 2)
let xz = XZ(x: 3, z: 4)

//let xs = [xy, xz] // error
let xs = [AnyX(xy), AnyX(xz)]
xs.forEach { print($0.x) } // 1 3

12

私が見つけた限られた解決策は、プロトコルをクラス専用プロトコルとしてマークすることです。これにより、「===」演算子を使用してオブジェクトを比較できます。これは構造体などでは機能しないことを理解していますが、私の場合はそれで十分でした。

protocol SomeProtocol: class {
    func bla()
}

class SomeClass {

    var protocols = [SomeProtocol]()

    func addElement(element: SomeProtocol) {
        self.protocols.append(element)
    }

    func removeElement(element: SomeProtocol) {
        for i in 0...protocols.count {
            if protocols[i] === element {
                protocols.removeAtIndex(i)
                return
            }
        }
    }

}

同じオブジェクトでが2回以上呼び出されたprotocols場合、これはの重複エントリを許可しませんaddElementか?
トムハリントン

はい、swiftの配列には重複したエントリが含まれる場合があります。これがコードで発生する可能性があると思われる場合は、配列の代わりにSetを使用するか、配列にそのオブジェクトが含まれていないことを確認してください。
アルマス

removeElement()重複を避けたい場合は、新しい要素を追加する前に呼び出すことができます。
Georgios

つまり、アレイを制御する方法が空中にあるということですか?答えてくれてありがとう
レイモンドヒル

9

解決策は非常に簡単です。

protocol SomeProtocol {
    func bla()
}

class SomeClass {
    init() {}

    var protocols = [SomeProtocol]()

    func addElement<T: SomeProtocol where T: Equatable>(element: T) {
        protocols.append(element)
    }

    func removeElement<T: SomeProtocol where T: Equatable>(element: T) {
        protocols = protocols.filter {
            if let e = $0 as? T where e == element {
                return false
            }
            return true
        }
    }
}

4
あなたは重要なことを見逃しました:OPはプロトコルがプロトコルを継承することを望んでいEquatableます。それは大きな違いを生みます。
ワルダイバー2016

@werediver私はそうは思いません。彼SomeProtocolは、型指定された配列に準拠するオブジェクトを格納したいと考えています。Equatable適合は、配列から要素を削除する場合にのみ必要です。私のソリューションは@almasソリューションの改良版Equatableです。これは、プロトコルに準拠する任意のSwiftタイプで使用できるためです。
bzz

2

あなたの主な目的は、いくつかのプロトコルに準拠したオブジェクトのコレクションを保持し、このコレクションに追加して削除することだと思います。これは、クライアント「SomeClass」で述べられている機能です。公平な継承には自己が必要であり、この機能には必要ありません。カスタムコンパレーターを使用できる「インデックス」関数を使用して、Obj-Cの配列でこれを機能させることもできましたが、これはSwiftではサポートされていません。したがって、最も簡単な解決策は、以下のコードに示すように、配列ではなく辞書を使用することです。必要なプロトコル配列を返すgetElements()を提供しました。そのため、SomeClassを使用しているユーザーは、実装に辞書が使用されていることさえ知りません。

いずれの場合でも、オブジェクトを区別するためにいくつかの区別できるプロパティが必要になるため、「名前」であると想定しました。新しいSomeProtocolインスタンスを作成するときは、do element.name = "foo"であることを確認してください。名前が設定されていない場合でもインスタンスを作成できますが、インスタンスはコレクションに追加されず、addElement()は「false」を返します。

protocol SomeProtocol {
    var name:String? {get set} // Since elements need to distinguished, 
    //we will assume it is by name in this example.
    func bla()
}

class SomeClass {

    //var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if
     // There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift
    /*
    static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool {
        if (one.name == nil) {return false}
        if(toTheOther.name == nil) {return false}
        if(one.name ==  toTheOther.name!) {return true}
        return false
    }
   */

    //The best choice here is to use dictionary
    var protocols = [String:SomeProtocol]()


    func addElement(element: SomeProtocol) -> Bool {
        //self.protocols.append(element)
        if let index = element.name {
            protocols[index] = element
            return true
        }
        return false
    }

    func removeElement(element: SomeProtocol) {
        //if let index = find(self.protocols, element) { // find not suported in Swift 2.0


        if let index = element.name {
            protocols.removeValueForKey(index)
        }
    }

    func getElements() -> [SomeProtocol] {
        return Array(protocols.values)
    }
}

0

そのブログ投稿で、純粋ではない Swiftソリューションを見つけました。 http //blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

コツはNSObjectProtocol、それが紹介するように準拠することisEqual()です。したがって、Equatableプロトコルとそのデフォルトの使用法の代わりに==、独自の関数を記述して要素を見つけて削除することができます。

ここにあなたのfind(array, element) -> Int?関数の実装があります:

protocol SomeProtocol: NSObjectProtocol {

}

func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? {
    for (index, object) in protocols.enumerated() {
        if (object.isEqual(element)) {
            return index
        }
    }

    return nil
}

注:この場合、準拠するオブジェクトはSomeProtocolから継承する必要がありますNSObject

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