Swift Language
Generics
Sök…
Anmärkningar
Med generisk kod kan du skriva flexibla, återanvändbara funktioner och typer som kan fungera med alla typer, förutsatt att du definierar krav. Du kan skriva kod som undviker duplicering och uttrycker sin avsikt på ett tydligt, abstrakt sätt.
Generics är en av de kraftfullaste funktionerna i Swift, och mycket av Swift-standardbiblioteket är byggt med generisk kod. Till exempel är Swift's
Array
ochDictionary
typer båda generiska samlingar. Du kan skapa en matris som innehållerInt
värden, eller en matris som innehållerString
, eller verkligen en matris för någon annan typ som kan skapas i Swift. På liknande sätt kan du skapa en ordlista för att lagra värden av vilken specifik typ som helst, och det finns inga begränsningar för vad den typen kan vara.
Begränsa generiska platshållartyper
Det är möjligt att tvinga typparametrarna för en generisk klass att implementera ett protokoll , till exempel 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
}
}
När vi skapar en ny MyGenericClass
måste Equatable
implementera Equatable
protokollet (se till att typparametern kan jämföras med en annan variabel av samma typ med ==
)
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"))
Grunderna i generiker
Generics är platshållare för typer, så att du kan skriva flexibel kod som kan tillämpas på flera typer. Fördelen med att använda generika jämfört med Any
är att de fortfarande tillåter kompilatorn att säkerställa stark typsäkerhet.
En generisk platshållare definieras inom vinkelfästen <>
.
Generiska funktioner
För funktioner placeras denna platshållare efter funktionsnamnet:
/// 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
}
I detta fall är den generiska platshållaren T
När du kommer att ringa funktionen kan Swift dra slutsatsen för typen av T
för dig (eftersom den helt enkelt fungerar som en platshållare för en faktisk typ).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Här överför vi två heltal till funktionen. Därför drar Swift slutsatsen T == Int
- alltså dras funktionssignaturen ut för att vara (Int, Int) -> Int
.
På grund av den starka typsäkerheten som generics erbjuder - måste både argumenten och återlämnandet av funktionen vara av samma typ. Därför kommer följande inte att sammanställas:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
Generiska typer
För att använda generika med klasser , strukturer eller enums kan du definiera den generiska platshållaren efter typnamnet.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
Denna generiska platshållare kräver en typ när du kommer att använda Bar
. I detta fall kan man dra slutsatsen från initialiseraren init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Här sluts den generiska platshållaren T
att vara av typen String
, vilket skapar en Bar<String>
-instans. Du kan också specificera typen uttryckligen:
let bar = Bar<String>(baz: "a string")
När den används med en typ kommer den givna generiska platshållaren att behålla sin typ under hela den aktuella instansens livslängd och kan inte ändras efter initialisering. Därför när du baz
fastigheten baz
, kommer den alltid att vara av typen String
för den här instansen.
let str = bar.baz // of type String
Passerar runt generiska typer
När du kommer fram till generiska typer måste du i de flesta fall vara uttrycklig för den generiska platshållartyp du förväntar dig. Till exempel som funktionsinmatning:
func takeABarInt(bar:Bar<Int>) {
...
}
Denna funktion accepterar endast en Bar<Int>
. Att försöka passera i en Bar
instans där den generiska platshållartypen inte är Int
kommer att resultera i ett kompilatorfel.
Generisk platshållare-namngivning
Generiska platshållarnamn är inte bara begränsade till enstaka bokstäver. Om en given platshållare representerar ett meningsfullt begrepp, bör du ge det ett beskrivande namn. Till exempel har Swift's Array
en generisk platshållare som heter Element
, som definierar elementtypen för en given Array
instans.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
Exempel på generiska klass
En generisk klass med typparametern 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
}
}
Vi kan nu skapa nya objekt med hjälp av en typparameter
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 kan också skapas med flera typer av parametrar
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}
}
Och används på samma sätt
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
Generisk klassärvning
Generiska klasser kan ärvas:
// 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
}
}
Använda Generics för att förenkla Array-funktioner
En funktion som utökar matrisens funktionalitet genom att skapa en objektorienterad borttagningsfunktion.
// 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")
}
}
}
Användande
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
Använd funktionen för att ta bort ett element utan behov av ett index. Passera bara objektet för att ta bort.
myArray.remove(2)
print(myArray)
// Prints [1,3]
Använd generika för att förbättra typsäkerheten
Låt oss ta detta exempel utan att använda generika
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
Protokolldeklarationen verkar bra om du inte använder den.
let myTestObject = TestObject.from(myJson) as? TestObject
Varför måste du kasta resultatet till TestObject
? På grund av vilken som Any
returtyp i protokolldeklarationen.
Genom att använda generika kan du undvika detta problem som kan orsaka runtime-fel (och vi vill inte ha dem!)
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?`
Avancerade typbegränsningar
Det är möjligt att ange flera typbegränsningar för generiska med hjälp av where
klausulen:
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.")
}
}
Det är också giltigt att skriva where
klausulen efter argumentlistan:
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.")
}
}
Tillägg kan begränsas till typer som uppfyller villkoren. Funktionen är endast tillgänglig för fall som uppfyller typvillkoren:
// "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")
}
}
}