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 und Dictionary Typen von Swift generische Sammlungen. Sie können ein Array erstellen, das Int Werte enthält, oder ein Array, das String 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")
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow