Saya ingin membuat kelas yang dapat menyimpan objek yang sesuai dengan protokol tertentu. Objek harus disimpan dalam array yang diketik. Menurut protokol dokumentasi Swift dapat digunakan sebagai tipe:
Karena ini adalah tipe, Anda dapat menggunakan protokol di banyak tempat di mana tipe lain diperbolehkan, termasuk:
- Sebagai tipe parameter atau tipe kembalian dalam sebuah fungsi, metode, atau penginisialisasi
- Sebagai jenis konstanta, variabel, atau properti
- Sebagai tipe item dalam larik, kamus, atau wadah lainnya
Namun yang berikut ini menghasilkan kesalahan kompilator:
Protokol 'SomeProtocol' hanya dapat digunakan sebagai batasan umum karena memiliki Persyaratan Jenis Sendiri atau terkait
Bagaimana Anda bisa menyelesaikan ini:
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)
}
}
}
Jawaban:
Anda telah mengalami varian masalah dengan protokol di Swift yang belum ada solusi yang baik.
Lihat juga Memperluas Array untuk memeriksa apakah itu diurutkan di Swift? , berisi saran tentang cara mengatasinya yang mungkin sesuai untuk masalah spesifik Anda (pertanyaan Anda sangat umum, mungkin Anda dapat menemukan solusi menggunakan jawaban ini).
sumber
Anda ingin membuat kelas generik, dengan batasan tipe yang mengharuskan kelas yang digunakan dengannya sesuai
SomeProtocol
, seperti ini: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) } } }
sumber
SomeProtocol
-let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
MyMemberClass
ke array?let foo = SomeClass<MyMemberClass>()
Equatable
kesesuaian - tanpanya Anda dapat menggunakan kode persis Anda. Mungkin mengajukan bug / permintaan fitur?Di Swift ada kelas protokol khusus yang tidak memberikan polimorfisme atas jenis yang mengimplementasikannya. Protokol semacam itu menggunakan
Self
atauassociatedtype
kata kunci dalam definisinya (danEquatable
merupakan salah satunya).Dalam beberapa kasus, dimungkinkan untuk menggunakan pembungkus tipe-terhapus untuk membuat koleksi Anda homomorfik. Berikut ini contohnya.
// 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
sumber
Solusi terbatas yang saya temukan adalah menandai protokol sebagai protokol khusus kelas. Ini akan memungkinkan Anda untuk membandingkan objek menggunakan operator '==='. Saya mengerti ini tidak akan berfungsi untuk struct, dll, tetapi itu cukup baik dalam kasus saya.
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 } } } }
sumber
protocols
, jikaaddElement
dipanggil lebih dari sekali dengan objek yang sama?removeElement()
sebelum menambahkan elemen baru jika Anda ingin menghindari duplikat.Solusinya cukup sederhana:
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 } } }
sumber
Equatable
protokol. Itu membuat perbedaan besar.SomeProtocol
tipe array.Equatable
kesesuaian diperlukan hanya untuk menghapus elemen dari larik. Solusi saya adalah versi perbaikan dari solusi @almas karena dapat digunakan dengan semua jenis Swift yang sesuai denganEquatable
protokol.Saya menganggap bahwa tujuan utama Anda adalah menyimpan koleksi objek yang sesuai dengan beberapa protokol, menambah koleksi ini dan menghapusnya. Ini adalah fungsionalitas seperti yang dinyatakan dalam klien Anda, "SomeClass". Pewarisan yang setara membutuhkan diri sendiri dan itu tidak diperlukan untuk fungsi ini. Kami dapat membuat ini berfungsi dalam array di Obj-C menggunakan fungsi "index" yang dapat menggunakan komparator kustom tetapi ini tidak didukung di Swift. Jadi solusi paling sederhana adalah dengan menggunakan kamus alih-alih array seperti yang ditunjukkan pada kode di bawah ini. Saya telah menyediakan getElements () yang akan memberi Anda kembali array protokol yang Anda inginkan. Jadi siapa pun yang menggunakan SomeClass tidak akan tahu bahwa kamus digunakan untuk implementasi.
Karena bagaimanapun, Anda akan memerlukan beberapa properti pembeda untuk memisahkan keberatan Anda, saya berasumsi itu adalah "nama". Harap pastikan bahwa do element.name = "foo" Anda saat membuat instance SomeProtocol baru. Jika namanya tidak disetel, Anda masih bisa membuat instance, tetapi tidak akan ditambahkan ke collection dan addElement () akan mengembalikan "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) } }
sumber
Saya menemukan solusi Swift yang tidak murni-murni di entri blog itu: http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/
Triknya adalah menyesuaikan diri dengan
NSObjectProtocol
yang diperkenalkanisEqual()
. Oleh karena itu, alih-alih menggunakanEquatable
protokol dan penggunaan defaultnya,==
Anda dapat menulis fungsi Anda sendiri untuk menemukan elemen dan menghapusnya.Berikut adalah implementasi dari
find(array, element) -> Int?
fungsi Anda :protocol SomeProtocol: NSObjectProtocol { } func find(protocols: [SomeProtocol], element: SomeProtocol) -> Int? { for (index, object) in protocols.enumerated() { if (object.isEqual(element)) { return index } } return nil }
Catatan: Dalam hal ini objek Anda yang sesuai dengan
SomeProtocol
must inherit fromNSObject
.sumber