Swift 5: Implementar operações de array genéricas em estruturas que implementam Equatable com protocolo

Oct 08 2020

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

2 RobNapier Oct 08 2020 at 07:18

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)