Swift 5: Implementar operações de array genéricas em estruturas que implementam Equatable com protocolo
Tenho 2 estruturas simples em meu aplicativo que estão em conformidade com um protocolo, para permitir algumas operações genéricas que preciso aplicar em ambos os tipos:
import UIKit
protocol Track {
var url: URL { get set }
}
struct TrackFromMediaFile: Track {
var url: URL
}
struct TrackFromAsset: Track {
var url: URL
}
Agora tenho uma lista dessas estruturas:
var tracks = [Track]()
tracks.append(TrackFromMediaFile(url: URL(string: "http://www.remotefile")!))
tracks.append(TrackFromAsset(url: URL(string: "local://localfile")!))
Preciso encontrar o índice de uma faixa específica nesta lista de faixas:
// 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)
Tentei seguir abordagens de https://stackoverflow.com/a/46719045/319905 e https://developer.apple.com/videos/play/wwdc2015/408/ sem sucesso, não consegui adaptá-los ao meu exemplo.
Como posso implementar o manuseio de matrizes de faixas de maneira genérica?
Respostas
Não está muito claro que tipo de igualdade você está procurando. Se você quiser verificar a igualdade de tipo e igualdade de valor, faça isso desta forma:
tracks.firstIndex(where: {
type(of: $0) == type(of: track) && $0.url == track.url
})
Se você quiser apenas validar se urlé igual, você faria desta forma:
tracks.firstIndex(where: { $0.url == track.url })
Em Swift, Equatable significa que um valor pode ser comparado a outros valores do mesmo tipo. Não se aplica a valores de tipos diferentes. Um protocolo não é um tipo, ele descreve tipos, portanto, a trilha em si não pode ser igualável.
Se você vai fazer muito isso, muitas vezes é conveniente expressar "capaz de verificar a igualdade com outros membros do protocolo". Isso não é igualável, mas certamente é algo que você pode expressar. Se parece com isso:
protocol Track {
var url: URL { get set }
func isEqual(to: Track) -> Bool
}
Para tipos em conformidade com a faixa que já são equivalentes, você pode fornecer uma implementação padrão:
extension Track where Self: Equatable {
func isEqual(to other: Track) -> Bool {
guard let other = other as? Self else { return false }
return self == other
}
}
E marque seus tipos de conformidade como Equatable (o swift criará automaticamente as conformidades):
struct TrackFromMediaFile: Track, Equatable { ... }
struct TrackFromAsset: Track, Equatable { ... }
Com isso, o código é um pouco melhor:
tracks.firstIndex(where: { $0.isEqual(to: track) })
E se você faz muito isso, é claro que pode torná-lo ainda mais agradável:
extension Collection where Element == Track {
func firstIndex(of track: Track) {
firstIndex(where: { $0.isEqual(to: track) })
}
}
E com isso, você pode ter a sintaxe com a qual começou:
tracks.firstIndex(of: track)