特定のプロトコルに準拠したオブジェクトを格納できるクラスを作成したい。オブジェクトは型付き配列に格納する必要があります。 Swiftによると、ドキュメントプロトコルは型として使用できます。
これは型であるため、次のような他の型が許可されている多くの場所でプロトコルを使用できます。
- 関数、メソッド、または初期化子のパラメーター型または戻り値型として
- 定数、変数、またはプロパティのタイプとして
- 配列、辞書、またはその他のコンテナ内のアイテムのタイプとして
ただし、以下はコンパイラエラーを生成します。
プロトコル「SomeProtocol」は、自己または関連するタイプの要件があるため、一般的な制約としてのみ使用できます
これをどのように解決するのですか?
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)
}
}
}
あなたはSwiftのプロトコルで問題の変種を見つけましたが、それに対する良い解決策はまだありません。
Swiftでソートされているかどうかを確認するための配列の拡張? も参照してください、特定の問題に適している可能性のある回避方法の提案が含まれていますこれらの回答を使用した回避策)。
次のように、使用するクラスが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)
}
}
}
Swiftには、それを実装する型に対してポリモーフィズムを提供しないプロトコルの特別なクラスがあります。そのようなプロトコルは、その定義でSelf
またはassociatedtype
キーワードを使用します(そしてEquatable
はそれらの1つです)。
場合によっては、型を消去したラッパーを使用して、コレクションを準同型にすることができます。以下に例を示します。
// 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
私が見つけた限られた解決策は、プロトコルをクラス専用プロトコルとしてマークすることです。これにより、「===」演算子を使用してオブジェクトを比較できます。これは構造体などでは機能しないことは理解していますが、私の場合は十分でした。
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
}
}
}
}
解決策は非常に簡単です。
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
}
}
}
あなたの主な目的は、あるプロトコルに準拠したオブジェクトのコレクションを保持し、このコレクションに追加し、そこから削除することだと思います。これは、クライアント「SomeClass」に記載されている機能です。公平な継承にはselfが必要であり、この機能には必要ありません。カスタムコンパレータを使用できる「インデックス」関数を使用して、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)
}
}
そのブログ投稿でnotpure-pure Swiftソリューションを見つけました: http://blog.inferis。 org/blog/2015/05/27/Swift-an-array-of-protocols /
トリックは、isEqual()
を導入するときにNSObjectProtocol
に準拠することです。したがって、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
から継承する必要があります。