Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Protocolli

Utilizzare i protocolli su Swift, utili per implementare il polimorfismo e quelle strutture che, in altri linguaggi general purpose come C++ o Java, sono sono note come interfacce.
Utilizzare i protocolli su Swift, utili per implementare il polimorfismo e quelle strutture che, in altri linguaggi general purpose come C++ o Java, sono sono note come interfacce.
Link copiato negli appunti

Un protocollo definisce una serie di requisiti, in termini di proprietà e metodi, necessari a soddisfare un compito specifico o una funzionalità. Esso non definisce di fatto alcuna implementazione, ma ne descrive l'interfaccia, elencando nomi, tipi e parametri di proprietà e metodi.
Un tipo, come una classe, struttura o enumerazione, che soddisfa i requisiti del protocollo si dice conforme a quel protocollo, e viceversa che il protocollo è adottato da quel tipo.

Facciamo subito un esempio, definendo il protocollo Marciante:

protocol Marciante {
    var marcia: Int {get set}
    func scala()
    func aumenta()
}

Questo procollo definisce una proprietà marcia con il valore della marcia corrente, e due metodi in grado di variare la marcia. Si nota che nessuna implementazione viene definita per i metodi scala e aumenta, né i valori iniziali per la proprietà marcia. La sintassi del costrutto protocollo prevede che, per le proprietà, è necessario indicare se esse sono in sola lettura (get) e/o modificabili (set).

Definiamo adesso due classi Auto e Vespa50 entrambe conformi al protocollo Marciante, indicando il nome del protocollo subito dopo il nome della classe, in modo simile ad una classe ereditata:

class Auto: Marciante {
    var marcia: Int = 0
    init(marciaIniziale: Int) {
        self.marcia = marciaIniziale
    }
    func scala() {
        if marcia > 1 {
            print("freno motore auto")
            marcia -= 1
        }
    }
    func aumenta() {
        if marcia < 6 {
            print("abbassiamo il numero di giri")
            marcia += 1
        }
    }
}
class Vespa50: Marciante {
    var marcia: Int = 0
    init(marciaIniziale: Int) {
        self.marcia = marciaIniziale
    }
    func scala() {
        if marcia > 1 {
            print("freno motore vespa")
            marcia -= 1
        }
    }
    func aumenta() {
        if marcia < 4 {
            print("passeggiata in relax")
            marcia += 1
        }
    }
}

Ogni classe deve necessariamente implementare i requisiti del protocollo, ma l'implementazione di ognuna di queste può essere completamente diversa. Il meccanismo dei protocolli è simile a quello delle interfacce dei linguaggi Java o C#.

A differenza che per l'ereditarietà, dove una classe può avere una singola superclasse, ogni classe può adottare più di un protocollo contemporaneamente.

Protocolli come tipi

Nonostante un protocollo non implementi direttamente alcuna funzionalità, esso può essere utilizzato a tutti gli effetti come un tipo, ad esempio come tipo di un parametro di funzioni e metodi o del valore di ritorno, di costanti, variabili e proprietà, degli elementi di array e dizionari. Ogni elemento dichiarato con il tipo del protocollo, può essere assegnato ad una istanza di una classe (struttura o enumerazione) che è conforme al protocollo. I protocolli sono dunque un altro mezzo per implementare il meccanismo del polimorfismo che abbiamo visto in una delle lezioni precedenti.

Facciamo un esempio:

var a = Auto(marciaIniziale: 1)
var v = Vespa50(marciaIniziale: 2)

a e v sono due istanze delle classi Auto e Vespa50. Entrambe queste classi sono conformi al protocollo Marciante. Possiamo dunque definire un array i cui elementi sono di tipo Marciante e assegnarvi le due istanze appena create:

var mezzi: [Marciante] = [a, v]

Se adesso iteriamo sull'array mezzi:

for i in mezzi {
    i.scala()
}

possiamo invocare il metodo scala(): Swift utilizzerà polimorficamente l'implementazione corretta.

Estensioni e protocolli

È possibile utilizzare un'estensione per rendere conforme una classe esistente ad uno o più protocolli.

Supponiamo di voler aggiungere un nuovo protocollo Stampabile alle classi Auto e Vespa50:

protocol Stampabile {
    func descrizione() -> String
}

Basta elencare il/i protocollo/i nella definizione dell'estensione e implementarne i metodi richiesti:

extension Vespa50: Stampabile {
    func descrizione() -> String {
        return "Sono una Vespa 50 in \(marcia)"
    }
}
extension Auto: Stampabile {
    func descrizione() -> String {
        return "Sono un auto in \(marcia)"
    }
}

Creaiamo un array di Stampabile e invochiamone il metodo descrizione():

var mezziStampabili: [Stampabile] = [a, v]
for j in mezziStampabili {
    print(j.descrizione())
}

Requisiti opzionali

Come abbiamo visto negli esempi precedenti, ogni classe che adotta un protocollo deve implementare tutti i requisiti richiesti. In Swift esiste la possibilità di indicare che alcuni di questi requisiti (metodi o proprietà) siano opzionali, anteponendo la parola riservata optional all'elemento opzionale:

@objc protocol Rottamabile {
    var stato: Bool { get }
    @objc optional func valorePermuta() -> Int
}

Nell'esempio precedente, il metodo valorePermuta() non deve essere necessariamente implementato per le classi conformi a Rottamabile.

Importante: se un protocollo dichiara requisiti opzionali è obbligatorio usare il modificatore @objc, sia per il protocollo che per il requisito dichiarato opzionale, anche se il progetto che usa tale codice non viene integrato con codice in Objective-C. Anche la classe che implementa il protocollo deve utilizzare il modificatore @objc.

Definiamo adesso due classi conformi al protocollo Rottamabile:

class Furgone: Auto, Rottamabile {
    var stato = false
}
class Autocarro: Auto, Rottamabile {
    var stato = true
    func valorePermuta() -> Int {
        return 10000
    }
}

Si noti che entrambe le classi ereditano dalla superclasse Auto (che a sua volta è conforme a Marciante e Stampabile) e adottano il protocollo Rottamabile. Solo la classe Autocarro implementa il metodo opzionale valutaPermuta().

Creiamo adesso un array di Rottamabile e iterando su di esso accediamone allo stato e, nel caso questo sia veritiero, invochiamo il metodo valorePermuta() di ognuno dei suoi elementi:

var f = Furgone(marciaIniziale: 3)
var ac = Autocarro(marciaIniziale: 3)
var daRottamare: [Rottamabile] = [f, ac]
for r in daRottamare {
    if r.stato {
        if let valore = r.valorePermuta?() {
            print(valore)
        }
    }
}

Si noti la sintassi usata per verificare se un oggetto di una classe conforme ad un protocollo implementa effettivamente un requisito opzionale. L'uso del simbolo ? dopo il nome del metodo si chiama optional chaining: il metodo verrà eseguito solo se valorePermuta è stato effettivamente definito; in caso contrario, l'espressione r.valorePermuta? restituisce nil. Di fatto, il tipo del metodo valorePermuta è l'opzionale (() -> Int)? (si noti il ? fuori dalle parentesi), per cui è necessario usare la sintassi dell'optional binding.

Composizione di protocolli

Nell'esempio precedente abbiamo assegnato all'array daRottamare le istanze f e ac di tipo rispettivamente Furgone e Autocarro, che a loro volta adottano i protocolli Rottamabile, Stampabile e Marciante. Se proviamo a invocare ad esempio il metodo descrizione() su un elemento dell'array daRottamare, otterremo un errore. Questo perchè Swift è molto preciso riguardo ai tipi, e nonostante gli elementi dell'array siano di fatto conformi a Stampabile e Marciante, il compilatore permette di accedere soltanto ai metodi di Rottamabile in accordo alla nostra specifica definizione.

Possiamo tuttavia definire tipi composti a partire da più procolli, concatenando due o più procolli con il simbolo &.

Vediamo un esempio:

var tutto: [Marciante & Rottamabile & Stampabile] = [f, ac]

Adesso sull'array tutto sarà possibile invocare qualsiasi metodo definito nei sopraindicati protocolli:

for t in tutto {
    print(t.descrizione())
    print(t.marcia)
    if let valore = t.valorePermuta?() {
        print(valore)
    }
}

Il playground con tutti gli esempi di questo articolo è disponibile su GitHub.


Ti consigliamo anche