Swift Language
Generics
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
eDictionary
di Swift sono entrambi raccolte generiche. È possibile creare una matrice che contiene valoriInt
o una matrice che contiene valoriString
, 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.
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")
}
}
}