Swift 5: Implemente operaciones de matriz genéricas en estructuras que implementen Equatable con el protocolo

Oct 08 2020

Tengo 2 estructuras simples en mi aplicación que se ajustan a un protocolo, para habilitar algunas operaciones genéricas que necesito aplicar en ambos tipos:

import UIKit

protocol Track {
    var url: URL { get set }
}

struct TrackFromMediaFile: Track {
    var url: URL
}

struct TrackFromAsset: Track {
    var url: URL
}

Ahora tengo una lista de estas estructuras:

var tracks = [Track]()
tracks.append(TrackFromMediaFile(url: URL(string: "http://www.remotefile")!))
tracks.append(TrackFromAsset(url: URL(string: "local://localfile")!))

Necesito encontrar el índice de una pista específica en esta lista de pistas:

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

Traté de seguir enfoques desde https://stackoverflow.com/a/46719045/319905 y https://developer.apple.com/videos/play/wwdc2015/408/ en vano, no pude adaptarlos a mi ejemplo.

¿Cómo puedo implementar el manejo de matrices de pistas de forma genérica?

Respuestas

2 RobNapier Oct 08 2020 at 07:18

No está muy claro qué tipo de igualdad está buscando. Si desea verificar tanto la igualdad de tipos como la igualdad de valores, lo haría de esta manera:

tracks.firstIndex(where: {
    type(of: $0) == type(of: track) && $0.url == track.url
})

Si solo desea validar que urles igual, lo haría de esta manera:

tracks.firstIndex(where: { $0.url == track.url })

En Swift, Equatable significa que un valor se puede comparar con otros valores del mismo tipo. No se aplica a valores de diferentes tipos. Un protocolo no es un tipo, describe tipos, por lo que Track en sí no puede ser Equatable.

Si va a hacer mucho esto, a menudo es conveniente expresar "capaz de comprobar la igualdad con otros miembros del protocolo". Eso no es equiparable, pero ciertamente es algo que puedes expresar. Se parece a esto:

protocol Track {
    var url: URL { get set }
    func isEqual(to: Track) -> Bool
}

Para los tipos de seguimiento que ya son Equatable, puede proporcionar una implementación predeterminada:

extension Track where Self: Equatable {
    func isEqual(to other: Track) -> Bool {
        guard let other = other as? Self else { return false }
        return self == other
    }
}

Y marque sus tipos conformes como Equatable (swift creará automáticamente conformidades):

struct TrackFromMediaFile: Track, Equatable { ... }
struct TrackFromAsset: Track, Equatable { ... }

Con eso, el código es un poco mejor:

tracks.firstIndex(where: { $0.isEqual(to: track) })

Y si lo hace mucho, por supuesto, puede hacerlo un poco mejor aún:

extension Collection where Element == Track {
    func firstIndex(of track: Track) {
        firstIndex(where: { $0.isEqual(to: track) })
    }
}

Y con eso, puede tener la sintaxis con la que comenzó:

tracks.firstIndex(of: track)