Protokollerin seri türleri ve fonksiyon parametreleri olarak kullanımı


137

Belirli bir protokole uygun nesneleri depolayabilen bir sınıf oluşturmak istiyorum. Nesneler yazılı bir dizide saklanmalıdır. Swift dokümantasyon protokollerine göre tip olarak kullanılabilir: 

Bir tür olduğundan, aşağıdakiler de dahil olmak üzere diğer türlere izin verilen birçok yerde bir protokol kullanabilirsiniz:

  • Bir işlev, yöntem veya başlatıcıda parametre türü veya dönüş türü olarak
  • Sabit, değişken veya özellik türü olarak
  • Bir dizideki, sözlükteki veya başka bir kaptaki öğelerin türü olarak

Ancak aşağıdakiler derleyici hataları oluşturur:

'SomeProtocol' Protokolü yalnızca Öz veya ilişkili tür gereksinimleri olduğundan genel bir kısıtlama olarak kullanılabilir

Bunu nasıl çözmeniz gerekiyor:

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'te, onu uygulayan tipler üzerinde polimorfizm sağlamayan özel bir protokol sınıfı vardır. Bu tür protokoller tanımında Benlik veya ilişkili tür kullanır (ve Eşittir bunlardan biridir). Bazı durumlarda, koleksiyonunuzu homomorfik hale getirmek için tip silinmiş bir ambalaj kullanmak mümkündür. Örneğin buraya bakın .
werediver

Yanıtlar:


48

Swift'te henüz iyi bir çözüm bulunmayan protokollerle ilgili bir sorun varyantına ulaştınız.

Ayrıca Swift'te sıralanıp sıralanmadığını kontrol etmek için Diziyi Genişletme bölümüne bakın. , bu soruna, özel sorununuza uygun olabilecek bir çözüm önerileri içerir (sorunuz çok geneldir, belki de bu yanıtları kullanarak bir geçici çözüm bulabilirsiniz).


1
Sanırım şu an için doğru cevap bu. Nate'in çözümü çalışıyor ama sorunumu tamamen çözmüyor.
snod

32

Onunla kullanılan sınıfların aşağıdakilere uymasını gerektiren bir tür kısıtlamasıyla genel bir sınıf oluşturmak istiyorsunuz 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)
        }
    }
}

Bu sınıftaki bir nesneyi nasıl somutlaştırırsınız?
snod

Hmmm ... Bu şekilde, aşağıdakilere uyan tek bir tür kullanarak kilitlenir SomeProtocol-let protocolGroup: SomeClass<MyMemberClass> = SomeClass()
Nate Cook

Bu şekilde MyMemberClassdiziye yalnızca sınıf nesneleri ekleyebilir misiniz ?
snod

veyalet foo = SomeClass<MyMemberClass>()
DarkDust

@snod Evet, aradığınız şey bu değil. Sorun Equatableuyumdur - bu olmadan tam kodunuzu kullanabilirsiniz. Belki bir hata / özellik isteği gönderebilir?
Nate Cook

15

Swift'te, onu uygulayan tipler üzerinde polimorfizm sağlamayan özel bir protokol sınıfı vardır. Bu protokoller tanımlarında Selfveya associatedtypeanahtar sözcükleri kullanır (ve Equatablebunlardan biridir).

Bazı durumlarda, koleksiyonunuzu homomorfik hale getirmek için tip silinmiş bir ambalaj kullanmak mümkündür. Aşağıda bir örnek verilmiştir.

// 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

Bulduğum sınırlı çözüm, protokolü yalnızca sınıf protokolü olarak işaretlemektir. Bu, '===' operatörünü kullanarak nesneleri karşılaştırmanıza olanak tanır. Bunun yapılar vs. için işe yaramayacağını anlıyorum, ancak benim durumumda yeterince iyiydi.

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

}

Bu yinelenen girdileri izin vermez protocolseğer, addElementdaha aynı nesne ile bir kez daha deniyor?
Tom Harrington

Evet, hızlı diziler yinelenen girişler içerebilir. Kodunuzda bunun olabileceğini düşünüyorsanız, ya dizi yerine Set'i kullanın ya da dizinin o nesneyi içermediğinden emin olun.
almas

removeElement()Yinelemeleri önlemek istiyorsanız, yeni öğeyi eklemeden önce arayabilirsiniz .
Georgios

Demek istediđim dizinin havalandýđýný nasýl kontrol ediyorsun, deđil mi? Cevabınız için teşekkür ederiz
Reimond Hill

9

Çözüm oldukça basit:

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
Önemli şeyi kaçırdınız: OP protokolün protokolü devralmasını istiyor Equatable. Çok büyük bir fark yaratıyor.
werediver

@werediver sanmıyorum. SomeProtocolYazılan bir diziye uygun nesneleri saklamak istiyor . Equatableuyumluluk yalnızca dizideki öğeleri kaldırmak için gereklidir. Benim çözümüm, @almas çözümünün geliştirilmiş bir sürümüdür, çünkü Equatableprotokole uyan herhangi bir Swift türüyle kullanılabilir .
bzz

2

Ana amacınızın bazı protokole uygun bir nesne koleksiyonu tutmak, bu koleksiyona eklemek ve ondan silmek olduğunu düşünüyorum. Bu, istemcinizde belirtilen "SomeClass" işlevidir. Eşit kalıtım kendilik gerektirir ve bu işlev için gerekli değildir. Bu çalışmayı, özel bir karşılaştırıcı alabilen "index" işlevini kullanarak Obj-C dizilerinde yapabilirdik, ancak Swift'te desteklenmiyor. Bu nedenle en basit çözüm, aşağıdaki kodda gösterildiği gibi bir dizi yerine bir sözlük kullanmaktır. İstediğiniz protokol dizisini geri getirecek getElements () sağladım. Yani SomeClass kullanan herkes uygulama için bir sözlüğün kullanıldığını bile bilemez.

Her durumda, objelerinizi ayırmak için bazı ayırt edici özelliklere ihtiyacınız olacağından, bunun "isim" olduğunu varsaydım. Lütfen yeni bir SomeProtocol örneği oluştururken do element.name = "foo" öğenizin olduğundan emin olun. Ad ayarlanmamışsa, örneği yine de oluşturabilirsiniz, ancak koleksiyona eklenmez ve addElement () "false" değerini döndürür.

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

Ben buldum değil o blog yayınında saf saf Swift çözümü: http://blog.inferis.org/blog/2015/05/27/swift-an-array-of-protocols/

Hile, NSObjectProtocoltanıttığı şekliyle uymaktır isEqual(). Bu nedenle Equatableprotokolü kullanmak ve varsayılan kullanımı== , öğeyi bulmak ve kaldırmak için kendi işlevinizi yazabilirsiniz.

find(array, element) -> Int?İşlevinizin uygulanması :

protocol SomeProtocol: NSObjectProtocol {

}

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

    return nil
}

Not: Bu durumda, uygun nesnelerin SomeProtocolmiras alınması gerekir NSObject.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.