Swift 5: implementa operazioni di array generiche su strutture che implementano Equatable con il protocollo
Ho 2 semplici strutture nella mia app conformi a un protocollo, per abilitare alcune operazioni generiche che devo applicare su entrambi i tipi:
import UIKit
protocol Track {
var url: URL { get set }
}
struct TrackFromMediaFile: Track {
var url: URL
}
struct TrackFromAsset: Track {
var url: URL
}
Ora ho un elenco di queste strutture:
var tracks = [Track]()
tracks.append(TrackFromMediaFile(url: URL(string: "http://www.remotefile")!))
tracks.append(TrackFromAsset(url: URL(string: "local://localfile")!))
Ho bisogno di trovare l'indice di una traccia specifica in questo elenco di tracce:
// throws:
// Value of protocol type 'Track' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols
var track = tracks[0]
tracks.firstIndex(of: track)
Ho provato a seguire gli approcci da https://stackoverflow.com/a/46719045/319905 e https://developer.apple.com/videos/play/wwdc2015/408/ inutilmente, non ho potuto adattare questi al mio esempio.
Come posso implementare la gestione di array di tracce in modo generico?
Risposte
Non è del tutto chiaro che tipo di uguaglianza stai cercando. Se vuoi controllare sia l'uguaglianza di tipo che l'uguaglianza di valore, dovresti farlo in questo modo:
tracks.firstIndex(where: {
type(of: $0) == type(of: track) && $0.url == track.url
})
Se vuoi solo convalidare che urlè uguale, lo faresti in questo modo:
tracks.firstIndex(where: { $0.url == track.url })
In Swift, Equatable significa che un valore può essere confrontato con altri valori dello stesso tipo. Non si applica a valori di diverso tipo. Un protocollo non è un tipo, descrive i tipi, quindi la traccia stessa non può essere equiparabile.
Se hai intenzione di farlo spesso, è spesso conveniente esprimere "in grado di verificare l'uguaglianza con gli altri membri del protocollo". Non è Equatable, ma è certamente qualcosa che puoi esprimere. Assomiglia a questo:
protocol Track {
var url: URL { get set }
func isEqual(to: Track) -> Bool
}
Per i tipi conformi a Track che sono già Equatable, puoi fornire un'implementazione predefinita:
extension Track where Self: Equatable {
func isEqual(to other: Track) -> Bool {
guard let other = other as? Self else { return false }
return self == other
}
}
E contrassegna i tuoi tipi conformi come Equatable (swift creerà automaticamente le conformità):
struct TrackFromMediaFile: Track, Equatable { ... }
struct TrackFromAsset: Track, Equatable { ... }
Con questo, il codice è un po 'più carino:
tracks.firstIndex(where: { $0.isEqual(to: track) })
E se lo fai spesso, puoi ovviamente renderlo ancora un po 'più carino:
extension Collection where Element == Track {
func firstIndex(of track: Track) {
firstIndex(where: { $0.isEqual(to: track) })
}
}
E con questo, puoi avere la sintassi con cui hai iniziato:
tracks.firstIndex(of: track)