2015-01-07 17 views
6

Sto ancora combattendo con i generici di Swift. Oggi ho scoperto che la mia implementazione del protocollo Equitable non funziona, se è chiamata da una classe generica.L'implementazione equa non sembra funzionare con i generici

mia classe del modello:

func ==(lhs: Tracking, rhs: Tracking) -> Bool { 
    // This method never executes if called from BaseCache 
    return lhs.id == rhs.id 
} 

class Tracking: NSObject, Equatable, Printable { 
    var id: String? 
    ..... 
} 

Classe, che utilizza tipo generico:

class BaseCache<T: NSObject where T: Equatable, T: Printable> { 

    ..... 

    func removeEntities(entities: [T]) { 
     var indexesToRemove = [Int]() 
     for i in 0...allEntities.count - 1 { 
      let item = allEntities[i] 
      for entity in entities { 
       println("equal: \(entity == item)") 
       // FOR SOME REASONS THE STATEMENT BELOW IS ALWAYS FALSE 
       if entity == item { 
        indexesToRemove.append(i) 
        break 
       } 
      } 
     } 
     for index in indexesToRemove { 
      allEntities.removeAtIndex(index) 
     } 
     didRemoveEntities() 
    } 
} 

ed è sottoclasse:

class TrackingCache<T: Tracking>: BaseCache<Tracking> { 
} 

Quando chiamo removeEntities metodo TrackingCache esempio, ho sempre equal: false nell'output, anche se id s sono gli stessi.

Ma se sposto il metodo direttamente nella classe TrackingCache, sembra funzionare bene!

Qualche idea del perché questo sta accadendo e come risolvere questo problema?

+1

btw, invece di 'for i in 0 ... allEntities.count - 1' puoi scrivere' per i in 0 ..

+0

A seconda di cosa sia "allEntities", puoi alternativamente dire "per oggetto in tutte le Entità" '. – matt

+1

Ha bisogno di un indice, per guidare la rimozione più tardi. 'for (idx, item) in enumerate (allEntities)' funzionerebbe - si basa su indici 'Int', ma questo è il caso qui. –

risposta

12

Attenzione: poiché == non è una funzione membro, non fornirà la spedizione dinamica per impostazione predefinita, incluso se la si utilizza accanto a un segnaposto generico.

Si consideri il seguente codice:

class C: NSObject, Equatable { 
    let id: Int 
    init(_ id: Int) { self.id = id } 
} 

// define equality as IDs are equal 
func ==(lhs: C, rhs: C) -> Bool { 
    return lhs.id == rhs.id 
} 

// create two objects with the same ID 
let c1 = C(1) 
let c2 = C(1) 

// true, as expected 
c1 == c2 

OK ora creare due variabili di tipo NSObject, e assegnare gli stessi valori a loro:

let o1: NSObject = c1 
let o2: NSObject = c2 

// this will be false 
o1 == o2 

Perché? Perché stai invocando la funzione func ==(lhs: NSObject, rhs: NSObject) -> Bool, nonfunc ==(lhs: C, rhs: C) -> Bool. Quale funzione di overload da scegliere non è determinata dinamicamente in fase di esecuzione in base a ciò che o1 e o2 fanno riferimento a. È determinato da Swift in fase di compilazione, in base ai tipi di o1 e o2, che in questo caso è NSObject.

NSObject== diversa implementazione di tuoi pari - chiama lhs.isEqual(rhs), che ricade se non sostituita per controllare parità di riferimento (cioè sono i due riferimenti puntano allo stesso oggetto). Non lo sono, quindi non sono uguali.

Perché ciò accade con BaseCache ma non con TrackingCache? Perché BaseCache è definito come vincolante solo per NSObject, così T ha solo le capacità di un NSObject - simile a quando si è assegnato c1 ad una variabile di tipo NSObject, sarà chiamato la versione NSObject di ==.

TrackingCache sulle altre garanzie mano T sarà almeno un oggetto Tracking, quindi si utilizza la versione di == per il monitoraggio. Swift sceglierà il più "specifico" di tutti i possibili sovraccarichi - Tracking è più specifico della sua classe base, NSObject.

Ecco un esempio più semplice, solo con funzioni generiche:

func f<T: NSObject>(lhs: T, rhs: T) -> Bool { 
    return lhs == rhs 
} 

func g<T: C>(lhs: T, rhs: T) -> Bool { 
    return lhs == rhs 
} 

f(c1, c2) // false 
g(c1, c2) // true 

Se si vuole risolvere questo problema, è possibile ignorare isEqual:

class C: NSObject, Equatable { 
    ... 
    override func isEqual(object: AnyObject?) -> Bool { 
     return (object as? C)?.id == id 
    } 
} 

// this is now true: 
o1 == o2 
// as is this: 
f(c1, c2) 

Questa tecnica (dopo aver == chiamata un dinamicamente inviato metodo di classe) è anche un modo per implementare questo comportamento per le classi non NSObject. Le strutture, ovviamente, non hanno questo problema dal momento che non supportano l'ereditarietà - punteggio 1 per le strutture!