Ricerca…


Osservazioni

Il codice generico consente di scrivere funzioni e tipi flessibili e riutilizzabili che possono funzionare con qualsiasi tipo, in base ai requisiti definiti dall'utente. È possibile scrivere codice che evita la duplicazione ed esprime il suo intento in modo chiaro e astratto.

I generici sono una delle funzionalità più potenti di Swift e gran parte della libreria standard di Swift è costruita con codice generico. Ad esempio, i tipi di Array e Dictionary di Swift sono entrambi raccolte generiche. È possibile creare una matrice che contiene valori Int o una matrice che contiene valori String , o in effetti una matrice per qualsiasi altro tipo che può essere creato in Swift. Allo stesso modo, è possibile creare un dizionario per memorizzare valori di qualsiasi tipo specificato e non ci sono limitazioni su cosa possa essere quel tipo.

Fonte: Apple Swift Programming Language

Vincolare i tipi di segnaposto generici

È possibile forzare i parametri di tipo di una classe generica per implementare un protocollo , ad esempio, Equatable

class MyGenericClass<Type: Equatable>{
    
    var value: Type
    init(value: Type){
        self.value = value
    }
    
    func getValue() -> Type{
        return self.value
    }

    func valueEquals(anotherValue: Type) -> Bool{
        return self.value == anotherValue
    }
}

Ogni volta che creiamo una nuova MyGenericClass , il parametro type deve implementare il protocollo Equatable (assicurando che il parametro type possa essere confrontato con un'altra variabile dello stesso tipo usando == )

let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid

// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2]) 

let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false

// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))

Le basi di Generics

I generici sono segnaposti per i tipi, consentendo di scrivere codice flessibile che può essere applicato su più tipi. Il vantaggio dell'utilizzo di generici su Any è che consentono ancora al compilatore di rafforzare la sicurezza dei caratteri.

Un segnaposto generico è definito tra parentesi angolari <> .

Funzioni generiche

Per le funzioni , questo segnaposto viene posizionato dopo il nome della funzione:

/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
    return arc4random_uniform(2) == 0 ? a : b
}

In questo caso, il segnaposto generico è T Quando si arriva a chiamare la funzione, Swift può dedurre il tipo di T per te (in quanto agisce semplicemente come segnaposto per un tipo effettivo).

let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)

Qui stiamo passando due interi alla funzione. Quindi Swift sta inferendo T == Int - quindi la firma della funzione è dedotta per essere (Int, Int) -> Int .

A causa della forte sicurezza del tipo offerta dai generici, sia gli argomenti che il ritorno della funzione devono essere dello stesso tipo. Pertanto quanto segue non verrà compilato:

struct Foo {}

let foo = Foo()

let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'

Tipi generici

Per utilizzare i generici con classi , strutture o enumerazioni , è possibile definire il segnaposto generico dopo il nome del tipo.

class Bar<T> {
    var baz : T
    
    init(baz:T) {
        self.baz = baz
    }
}

Questo segnaposto generico richiede un tipo quando si usa la classe Bar . In questo caso, può essere dedotto dall'inizializzatore init(baz:T) .

let bar = Bar(baz: "a string") // bar's type is Bar<String>

Qui il segnaposto generico T è dedotto per essere di tipo String , creando così un'istanza Bar<String> . Puoi anche specificare il tipo esplicitamente:

let bar = Bar<String>(baz: "a string")

Se utilizzato con un tipo, il segnaposto generico specificato manterrà il suo tipo per l'intera durata dell'istanza data e non potrà essere modificato dopo l'inizializzazione. Pertanto, quando si accede alla proprietà baz , sarà sempre di tipo String per questa determinata istanza.

let str = bar.baz // of type String

Passando intorno ai tipi generici

Quando si arriva a passare tipi generici, nella maggior parte dei casi è necessario essere espliciti sul tipo di segnaposto generico che ci si aspetta. Ad esempio, come input di una funzione:

func takeABarInt(bar:Bar<Int>) {
    ...
}

Questa funzione accetta solo una Bar<Int> . Il tentativo di passare in un'istanza Bar cui il tipo generico di segnaposto non è Int causerà un errore del compilatore.

Denominazione generica del segnaposto

I nomi generici dei segnaposto non si limitano alle sole lettere singole. Se un determinato segnaposto rappresenta un concetto significativo, dovresti dargli un nome descrittivo. Ad esempio, Swift's Array ha un segnaposto generico chiamato Element , che definisce il tipo di elemento di una data istanza di Array .

public struct Array<Element> : RandomAccessCollection, MutableCollection {
    ...
}

Esempi di classi generiche

Una classe generica con il parametro type Type

class MyGenericClass<Type>{
 
    var value: Type
    init(value: Type){
        self.value = value
    }
 
    func getValue() -> Type{
        return self.value
    }
 
    func setValue(value: Type){
        self.value = value
    }
}

Ora possiamo creare nuovi oggetti usando un parametro di tipo

let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
 
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
 
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
 
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024

Generics può anche essere creato con più parametri di tipo

class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
 
    var value1: TypeOne
    var value2: TypeTwo
    var value3: TypeThree
    init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
        self.value1 = value1
        self.value2 = value2
        self.value3 = value3
    }
 
    func getValueOne() -> TypeOne{return self.value1}
    func getValueTwo() -> TypeTwo{return self.value2}
    func getValueThree() -> TypeThree{return self.value3}
}

E usato allo stesso modo

let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
 
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
 
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159

Eredità di classe generica

Le classi generiche possono essere ereditate:

// Models
class MyFirstModel {
}

class MySecondModel: MyFirstModel {
}

// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
    
    func doSomethingWithModel(model: T) {
        // Do something here
    }
    
}

class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
    
    override func doSomethingWithModel(model: T) {
        super.doSomethingWithModel(model)
        
        // Do more things here
    }
    
}

Utilizzo di Generics per semplificare le funzioni di array

Una funzione che estende la funzionalità dell'array creando una funzione di rimozione orientata agli oggetti.

// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
    /// Removes the given object from the array.
    mutating func remove(_ element: Element) {
        if let index = self.index(of: element ) {
            self.remove(at: index)
        } else {
            fatalError("Removal error, no such element:\"\(element)\" in array.\n")
        }
    }
}

uso

var myArray = [1,2,3]
print(myArray)

// Prints [1,2,3]

Utilizzare la funzione per rimuovere un elemento senza necessità di un indice. Basta passare l'oggetto per rimuovere.

myArray.remove(2)
print(myArray)

// Prints [1,3]

Usa i generici per migliorare la sicurezza del tipo

Prendiamo questo esempio senza usare i generici

protocol JSONDecodable {
    static func from(_ json: [String: Any]) -> Any?
}

La dichiarazione del protocollo sembra soddisfacente a meno che tu non la usi effettivamente.

let myTestObject = TestObject.from(myJson) as? TestObject

Perché devi TestObject il risultato a TestObject ? A causa del tipo Any ritorno nella dichiarazione del protocollo.

Usando i generici puoi evitare questo problema che può causare errori di runtime (e non vogliamo averli!)

protocol JSONDecodable {
    associatedtype Element 
    static func from(_ json: [String: Any]) -> Element?
}

struct TestObject: JSONDecodable {
    static func from(_ json: [String: Any]) -> TestObject? {
    }
}

let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`

Vincoli di tipo avanzato

E 'possibile specificare numerosi vincoli di tipo per i medicinali generici che utilizzano la where clausola:

func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
    // Access hashable function
    guard first.hashValue == second.hashValue else {
        return
    }
    // Access comparable function
    if first == second {
        print("\(first) and \(second) are equal.")
    }
}

E 'valido anche a scrivere la where clausola dopo la lista degli argomenti:

func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
    // Access hashable function
    guard first.hashValue == second.hashValue else {
        return
    }
    // Access comparable function
    if first == second {
        print("\(first) and \(second) are equal.")
    }
}

Le estensioni possono essere limitate a tipi che soddisfano le condizioni. La funzione è disponibile solo per le istanze che soddisfano le condizioni del tipo:

// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
    /// Removes the given object from the array.
    mutating func remove(_ element: Element) {
        // We could also use "self.index(of: element)" here, as "index(of:_)"
        // is also defined in an extension with "where Element: Equatable".
        // For the sake of this example, explicitly make use of the Equatable.
        if let index = self.index(where: { $0 == element }) {
            self.remove(at: index)
        } else {
            fatalError("Removal error, no such element:\"\(element)\" in array.\n")
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow