Zoeken…


Opmerkingen

Met generieke code kunt u flexibele, herbruikbare functies en typen schrijven die met elk type kunnen werken, afhankelijk van de vereisten die u definieert. U kunt code schrijven die duplicatie vermijdt en de bedoeling ervan op een duidelijke, abstracte manier uitdrukt.

Generics zijn een van de krachtigste functies van Swift, en veel van de standaard Swift-bibliotheek is gebouwd met generieke code. De typen Array en Dictionary Swift zijn bijvoorbeeld beide generieke collecties. U kunt een array maken met Int waarden, of een array met String , of een array voor elk ander type dat in Swift kan worden gemaakt. Op dezelfde manier kunt u een woordenboek maken om waarden van elk gespecificeerd type op te slaan, en er zijn geen beperkingen aan wat dat type kan zijn.

Bron: Apple's snelle programmeertaal

Generieke plaatsaanduidingstypen beperken

Het is mogelijk om de Equatable van een generieke klasse te dwingen een protocol te implementeren , bijvoorbeeld 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
    }
}

Wanneer we een nieuwe MyGenericClass , moet de parameter type het Equatable protocol implementeren (ervoor zorgen dat de parameter type kan worden vergeleken met een andere variabele van hetzelfde type met == )

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"))

De basis van generieken

Generics zijn tijdelijke aanduidingen voor typen, zodat u flexibele code kunt schrijven die op meerdere typen kan worden toegepast. Het voordeel van het gebruik van generieke geneesmiddelen ten opzichte van Any is dat hiermee de compiler nog steeds een sterke typeveiligheid kan afdwingen.

Een generieke tijdelijke aanduiding wordt gedefinieerd tussen punthaken <> .

Algemene functies

Voor functies wordt deze tijdelijke aanduiding achter de functienaam geplaatst:

/// 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 dit geval is de generieke tijdelijke aanduiding T Wanneer u de functie komt oproepen, kan Swift het type T voor u afleiden (omdat het eenvoudigweg fungeert als tijdelijke aanduiding voor een feitelijk type).

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

Hier geven we twee gehele getallen door aan de functie. Daarom leidt Swift T == Int - dus wordt de functiesignatuur afgeleid als (Int, Int) -> Int .

Vanwege de sterke typeveiligheid die generieke geneesmiddelen bieden, moeten zowel de argumenten als het rendement van de functie van hetzelfde type zijn. Daarom wordt het volgende niet gecompileerd:

struct Foo {}

let foo = Foo()

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

Generieke typen

Om generieken met klassen , structuren of enums te gebruiken , kunt u de generieke tijdelijke aanduiding achter de typenaam definiëren.

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

Deze generieke placeholder zal een type nodig als je naar de klas te gebruiken Bar . In dit geval kan het worden afgeleid uit de initialisator init(baz:T) .

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

Hier wordt afgeleid dat de generieke tijdelijke aanduiding T van het type String , waardoor een Bar<String> -instantie wordt gemaakt. U kunt het type ook expliciet opgeven:

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

Bij gebruik met een type, behoudt de gegeven generieke tijdelijke aanduiding zijn type voor de gehele levensduur van het gegeven exemplaar en kan niet worden gewijzigd na initialisatie. Wanneer u daarom toegang krijgt tot de eigenschap baz , is deze altijd van het type String voor deze gegeven instantie.

let str = bar.baz // of type String

Generieke typen doorgeven

Wanneer u generieke typen doorgeeft, moet u in de meeste gevallen expliciet zijn over het type generieke tijdelijke aanduiding dat u verwacht. Bijvoorbeeld, als een functie-invoer:

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

Deze functie accepteert alleen een Bar<Int> . Als u probeert te slagen in een Bar exemplaar waarbij het generieke type tijdelijke aanduiding niet Int is, resulteert dit in een compilerfout.

Generieke plaatsaanduiding

Algemene plaatsnamen zijn niet alleen beperkt tot enkele letters. Als een bepaalde tijdelijke aanduiding een betekenisvol concept vertegenwoordigt, moet u het een beschrijvende naam geven. Swift's Array heeft bijvoorbeeld een generieke tijdelijke aanduiding Element , die het elementtype van een bepaalde Array instantie definieert.

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

Algemene klassenvoorbeelden

Een generieke klasse met de parameter 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
    }
}

We kunnen nu nieuwe objecten maken met behulp van een parameter type

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 kunnen ook worden gemaakt met meerdere typeparameters

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}
}

En op dezelfde manier gebruikt

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

Overerving van generieke klasse

Algemene klassen kunnen worden overgenomen:

// 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
    }
    
}

Generics gebruiken om arrayfuncties te vereenvoudigen

Een functie die de functionaliteit van de array uitbreidt door een objectgeoriënteerde verwijderfunctie te maken.

// 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")
        }
    }
}

Gebruik

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

// Prints [1,2,3]

Gebruik de functie om een element te verwijderen zonder dat een index nodig is. Geef het te verwijderen object gewoon door.

myArray.remove(2)
print(myArray)

// Prints [1,3]

Gebruik generieke geneesmiddelen om de typeveiligheid te verbeteren

Laten we dit voorbeeld nemen zonder generieke geneesmiddelen te gebruiken

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

De protocolverklaring lijkt prima, tenzij u deze daadwerkelijk gebruikt.

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

Waarom moet je het resultaat naar TestObject ? Vanwege het Any return-type in de protocolverklaring.

Door generieke geneesmiddelen te gebruiken, kunt u dit probleem voorkomen dat runtime-fouten kan veroorzaken (en we willen ze niet hebben!)

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?`

Geavanceerde typebeperkingen

Het is mogelijk om verschillende typebeperkingen voor generieken op te geven met behulp van de where clausule:

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.")
    }
}

Het is ook geldig om de where clausule na de lijst met argumenten te schrijven:

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.")
    }
}

Uitbreidingen kunnen worden beperkt tot typen die aan voorwaarden voldoen. De functie is alleen beschikbaar voor instanties die voldoen aan de typevoorwaarden:

// "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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow