Swift Language
Generics
Suche…
Bemerkungen
Mit generischem Code können Sie flexible, wiederverwendbare Funktionen und Typen schreiben, die mit beliebigen Typen arbeiten können, abhängig von den von Ihnen definierten Anforderungen. Sie können Code schreiben, der Duplizierungen vermeidet und klar und abstrahiert.
Generics sind eine der leistungsstärksten Funktionen von Swift und ein Großteil der Swift-Standardbibliothek ist mit generischem Code erstellt. Zum Beispiel sind die
Array
undDictionary
Typen von Swift generische Sammlungen. Sie können ein Array erstellen, dasInt
Werte enthält, oder ein Array, dasString
Werte enthält, oder ein Array für einen anderen Typ, der in Swift erstellt werden kann. Auf ähnliche Weise können Sie ein Wörterbuch erstellen, um Werte eines beliebigen angegebenen Typs zu speichern. Es gibt keine Einschränkungen für den Typ dieses Typs.Quelle: Apples Swift-Programmiersprache
Einschränken von generischen Platzhaltertypen
Es ist möglich, die Typparameter einer generischen Klasse zu zwingen , ein Protokoll zu implementieren , beispielsweise 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
}
}
Immer wenn wir eine neue MyGenericClass
erstellen, muss der Typparameter das Equatable
Protokoll implementieren (wobei sicherzustellen ist, dass der Typparameter mit einer anderen Variablen desselben Typs mithilfe von ==
verglichen werden kann)
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"))
Die Grundlagen der Generika
Generics sind Platzhalter für Typen, mit denen Sie flexiblen Code schreiben können, der auf mehrere Typen angewendet werden kann. Die Verwendung von Generics gegenüber Any
Vorteil, dass der Compiler weiterhin die starke Typensicherheit durchsetzen kann.
Ein generischer Platzhalter wird in spitzen Klammern <>
.
Generische Funktionen
Bei Funktionen wird dieser Platzhalter nach dem Funktionsnamen platziert:
/// 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 diesem Fall ist der generische Platzhalter T
Wenn Sie die Funktion aufrufen, kann Swift den Typ von T
für Sie ableiten (da er einfach als Platzhalter für einen tatsächlichen Typ fungiert).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Hier übergeben wir zwei Ganzzahlen an die Funktion. Daher zieht Swift T == Int
Daraus wird abgeleitet, dass die Funktionssignatur (Int, Int) -> Int
.
Aufgrund der starken Typsicherheit, die Generics bieten, müssen sowohl die Argumente als auch die Rückgabe der Funktion vom selben Typ sein. Daher wird Folgendes nicht kompiliert:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
Generische Typen
Um Generics mit Klassen , Strukturen oder Enummen zu verwenden , können Sie den generischen Platzhalter nach dem Typnamen definieren.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
Dieser generische Platzhalter wird eine Art benötigen , wenn Sie die Klasse zu verwenden , kommen Bar
. In diesem Fall kann es vom Initialisierer init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Hier wird davon ausgegangen, dass der generische Platzhalter T
vom Typ String
, wodurch eine Bar<String>
-Instanz erstellt wird. Sie können den Typ auch explizit angeben:
let bar = Bar<String>(baz: "a string")
Bei Verwendung mit einem Typ behält der angegebene generische Platzhalter seinen Typ für die gesamte Lebensdauer der angegebenen Instanz bei und kann nach der Initialisierung nicht geändert werden. Wenn Sie also auf die Eigenschaft baz
zugreifen, hat diese Instanz immer den Typ String
.
let str = bar.baz // of type String
Übergeben von generischen Typen
Wenn Sie generische Typen umgehen, müssen Sie in den meisten Fällen den generischen Platzhaltertyp angeben, den Sie erwarten. Zum Beispiel als Funktionseingang:
func takeABarInt(bar:Bar<Int>) {
...
}
Diese Funktion akzeptiert nur eine Bar<Int>
. Wenn Sie versuchen, eine Bar
Instanz zu übergeben, bei der der generische Platzhaltertyp nicht Int
wird ein Compiler-Fehler ausgegeben.
Generische Platzhalterbenennung
Generische Platzhalternamen sind nicht nur auf einzelne Buchstaben beschränkt. Wenn ein bestimmter Platzhalter ein sinnvolles Konzept darstellt, sollten Sie ihm einen beschreibenden Namen geben. Das Array
Swift hat beispielsweise einen generischen Platzhalter namens Element
, der den Elementtyp einer gegebenen Array
Instanz definiert.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
Beispiele für generische Klassen
Eine generische Klasse mit dem Typparameter 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
}
}
Wir können jetzt neue Objekte mit einem Typparameter erstellen
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 können auch mit mehreren Typparametern erstellt werden
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}
}
Und auf die gleiche Weise verwendet
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
Generische Klassenvererbung
Generische Klassen können vererbt werden:
// 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
}
}
Verwenden von Generics zur Vereinfachung von Array-Funktionen
Eine Funktion, die die Funktionalität des Arrays durch Erstellen einer objektorientierten Remove-Funktion erweitert.
// 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")
}
}
}
Verwendungszweck
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
Verwenden Sie die Funktion, um ein Element ohne Index zu entfernen. Übergeben Sie einfach das Objekt, um es zu entfernen.
myArray.remove(2)
print(myArray)
// Prints [1,3]
Verwenden Sie Generika, um die Typsicherheit zu erhöhen
Nehmen wir dieses Beispiel ohne Verwendung von Generika
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
Die Protokolldeklaration scheint in Ordnung zu sein, sofern Sie sie nicht tatsächlich verwenden.
let myTestObject = TestObject.from(myJson) as? TestObject
Warum muss das Ergebnis in TestObject
? Wegen des Rückgabetyps Any
in der Protokolldeklaration.
Durch die Verwendung von Generics können Sie dieses Problem vermeiden, das Laufzeitfehler verursachen kann (und wir möchten sie nicht haben!)
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?`
Erweiterte Typbeschränkungen
Es ist möglich, mehrere Typeinschränkungen für Generics mithilfe der where
Klausel anzugeben:
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.")
}
}
Es ist auch zulässig, die where
Klausel nach der Argumentliste zu schreiben:
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.")
}
}
Erweiterungen können auf Typen beschränkt werden, die Bedingungen erfüllen. Die Funktion ist nur für Instanzen verfügbar, die die Typbedingungen erfüllen:
// "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")
}
}
}