Swift 5: implementa operazioni di array generiche su strutture che implementano Equatable con il protocollo

Oct 08 2020

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

2 RobNapier Oct 08 2020 at 07:18

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)