Hoe Generics in Swift te gebruiken

Met Generics kunt u een variabele declareren die bij uitvoering kan worden toegewezen aan een set typen die door ons zijn gedefinieerd.

In Swift kan een array gegevens van elk type bevatten. Als we een array van gehele getallen, strings of floats nodig hebben, kunnen we er een maken met de Swift-standaardbibliotheek. Het type dat de array moet behouden, wordt gedefinieerd wanneer het wordt gedeclareerd. Arrays zijn een bekend voorbeeld van generieke geneesmiddelen die worden gebruikt. Als u uw eigen verzameling zou implementeren, zou u absoluut generieke geneesmiddelen willen gebruiken. 

Laten we generieken onderzoeken en zien welke geweldige dingen ze ons toestaan ​​te doen.

1. Algemene functies

We beginnen met het creëren van een eenvoudige generieke functie. Ons doel is om een ​​functie te maken om te controleren of twee objecten van hetzelfde type zijn. Als ze van hetzelfde type zijn, maken we de waarde van het tweede object gelijk aan de waarde van het eerste object. Als ze niet van hetzelfde type zijn, zullen we "niet hetzelfde type" afdrukken. Hier is een poging om zo'n functie in Swift te implementeren.

func sameType (een: Int, inout two: Int) -> Void // Dit is altijd waar als (one.dynamicType == two.dynamicType) two = one else print ("not same type") 

In een wereld zonder generieke geneesmiddelen komen we een groot probleem tegen. Bij de definitie van een functie moeten we het type van elk argument specificeren. Als we daarom willen dat onze functie met alle mogelijke typen werkt, moeten we een definitie van onze functie schrijven met verschillende parameters voor elke mogelijke combinatie van typen. Dat is geen haalbare optie.

func sameType (een: Int, inout two: String) -> Void // Dit is altijd false als (one.dynamicType == two.dynamicType) two = one else print ("not same type") 

We kunnen dit probleem voorkomen door generieke geneesmiddelen te gebruiken. Neem een ​​kijkje in het volgende voorbeeld waarin we gebruikmaken van generieke geneesmiddelen.

func sameType(één: T, inout twee: E) -> Void if (one.dynamicType == two.dynamicType) two = one else print ("not same type")

Hier zien we de syntaxis van het gebruik van generieke geneesmiddelen. De generieke typen worden gesymboliseerd door T en E. De typen worden gespecificeerd door put in de definitie van onze functie, na de naam van de functie. Denken aan T en E als tijdelijke aanduidingen voor welk type we onze functie ook gebruiken.

Er is echter een groot probleem met deze functie. Het zal niet compileren. De compiler met gooi een fout, wat aangeeft dat T is niet converteerbaar naar E. Generics gaan ervan uit dat sinds T en E verschillende labels hebben, ze zullen ook verschillende typen zijn. Dit is prima, we kunnen ons doel nog steeds bereiken met twee definities van onze functie.

func sameType(één: T, inout twee: E) -> Void print ("not same type") func sameType(één: T, inout twee: T) -> Void two = one

Er zijn twee gevallen voor de argumenten van onze functie:

  • Als ze van hetzelfde type zijn, wordt de tweede implementatie aangeroepen. De waarde van twee is toegewezen aan een.
  • Als ze van verschillende typen zijn, wordt de eerste implementatie aangeroepen en wordt de tekenreeks "niet hetzelfde type" afgedrukt naar de console. 

We hebben onze functiedefinities verlaagd van een potentieel oneindig aantal combinaties van argumenttypen tot slechts twee. Onze functie werkt nu met elke combinatie van typen als argumenten.

var s = "apple" var p = 1 sameType (2, two: & p) print (p) sameType ("apple", two: & p) // Output: 1 "niet hetzelfde type"

Generiek programmeren kan ook op klassen en structuren worden toegepast. Laten we eens kijken hoe dat werkt.

2. Generieke klassen en structuren

Overweeg de situatie waarin we ons eigen gegevenstype willen maken, een binaire structuur. Als we een traditionele aanpak gebruiken waarbij we geen generieke gegevens gebruiken, maken we een binaire structuur die slechts één type gegevens kan bevatten. Gelukkig hebben we generieke medicijnen.

Een binaire boom bestaat uit knooppunten met:

  • twee kinderen of takken, die andere knooppunten zijn
  • een stuk gegevens dat het generieke element is
  • een ouderknooppunt dat meestal niet door het knooppunt wordt vermeld

Elke binaire boom heeft een hoofdknoop die geen ouders heeft. De twee kinderen worden gewoonlijk gedifferentieerd als linker en rechter knooppunten.

Alle gegevens in een linkerkind moeten kleiner zijn dan het bovenliggende knooppunt. Alle gegevens in het juiste kind moeten groter zijn dan het bovenliggende knooppunt.

klasse BTree  var data: T? = nil var links: BTree? = nil var right: BTree? = nul func invoegen (newData: T) if (self.data> newData) // invoegen in linker subtree else if (self.data < newData)  // Insert into right subtree  else if (self.data == nil)  self.data = newData return   

De verklaring van de btree klasse verklaart ook de generieke T, die wordt beperkt door de Vergelijkbaar protocol. We zullen protocollen en beperkingen een beetje bespreken.

Het gegevensitem van onze boom is gespecificeerd van het type T. Elk ingevoegd element moet ook van het type zijn T zoals gespecificeerd in de verklaring van de invoegen (_ :) methode. Voor een generieke klasse wordt het type opgegeven wanneer het object wordt gedeclareerd.

var tree: BTree

In dit voorbeeld maken we een binaire boom van gehele getallen. Een generieke klasse maken is vrij eenvoudig. Het enige dat we moeten doen is de generieke code in de verklaring opnemen en indien nodig in het lichaam vermelden.

3. Protocollen en beperkingen

In veel situaties moeten we arrays manipuleren om een ​​programmatisch doel te bereiken. Dit kan sorteren, zoeken, enzovoort zijn. We zullen kijken hoe generieke geneesmiddelen ons kunnen helpen bij het zoeken.

De belangrijkste reden waarom we een generieke functie gebruiken voor zoeken, is dat we een array willen kunnen doorzoeken, ongeacht het type objecten dat het bevat.

func vinden  (array: [T], item: T) -> Int? var index = 0 while (index < array.count)  if(item == array[index])  return index  index++  return nil; 

In het bovenstaande voorbeeld, de vinden (array: post :) functie accepteert een array van het generieke type T en zoekt het naar een overeenkomst met item die ook van het type is T.

Er is echter een probleem. Als u het bovenstaande voorbeeld probeert te compileren, geeft de compiler nog een fout. De compiler vertelt ons dat de binaire operator == kan niet op twee worden toegepast T operanden. De reden is duidelijk als je erover nadenkt. We kunnen niet garanderen dat het generieke type T ondersteunt de == operator. Gelukkig heeft Swift dit gedekt. Bekijk het bijgewerkte voorbeeld hieronder.

func vinden  (array: [T], item: T) -> Int? var index = 0 while (index < array.count)  if(item == array[index])  return index  index++  return nil; 

Als we opgeven dat het generieke type moet voldoen aan de gelijk te stellen protocol, dan geeft de compiler ons een pas. Met andere woorden, we passen een beperking toe op welke typen T kan vertegenwoordigen. Als u een beperking aan een generiek wilt toevoegen, geeft u de protocollen op tussen de punthaken.

Maar wat betekent het om iets te zijn? gelijk te stellen? Het betekent gewoon dat het de vergelijkingsoperator ondersteunt ==.

gelijk te stellen is niet het enige protocol dat we kunnen gebruiken. Swift heeft andere protocollen, zoals hashableen Vergelijkbaar. Wij zagen Vergelijkbaar eerder in het voorbeeld van de binaire boom. Als een type voldoet aan de Vergelijkbaar protocol, het betekent het < en > operators worden ondersteund. Ik hoop dat het duidelijk is dat je elk protocol dat je leuk vindt kunt gebruiken en het als een beperking kunt toepassen.

4. Protocollen definiëren

Laten we een voorbeeld van een game gebruiken om beperkingen en protocollen in actie te demonstreren. In elk spel hebben we een aantal objecten die in de loop van de tijd moeten worden bijgewerkt. Deze update zou kunnen zijn naar de positie, de gezondheid, etc. Laten we nu het voorbeeld van de gezondheid van het object gebruiken.

In onze implementatie van het spel, hebben we veel verschillende objecten met gezondheid die vijanden, bondgenoten, neutralen, enz. Zouden kunnen zijn. Ze zouden niet allemaal dezelfde klasse zijn als al onze verschillende objecten verschillende functies zouden kunnen hebben.

We willen graag een functie maken met de naam controleren(_:)om de gezondheid van een bepaald object te controleren en de huidige status bij te werken. Afhankelijk van de status van het object, kunnen we de gezondheid ervan veranderen. We willen dat deze functie werkt voor alle objecten, ongeacht hun type. Dit betekent dat we het moeten maken controleren(_:)een generieke functie. Door dit te doen, kunnen we de verschillende objecten doorlopen en bellen controleren(_:) op elk object.

Al deze objecten moeten een variabele hebben om hun gezondheid weer te geven en een functie om hun te veranderen levend -status. Laten we hier een protocol voor declareren en het een naam geven Gezond.

protocol Healthy mutating func setAlive (status: Bool) var health: Int get

Het protocol definieert welke eigenschappen en methoden van het type dat voldoet aan het protocol moet worden geïmplementeerd. Het protocol vereist bijvoorbeeld dat elk type dat voldoet aan de Gezond protocol implementeert het muteren setAlive (_ :) functie. Het protocol vereist ook een eigenschap met de naam Gezondheid.

Laten we nu opnieuw naar de controleren(_:) functie die we eerder hebben verklaard. We specificeren in de declaratie met een beperking dat het type T moet voldoen aan de Gezond protocol.

func check(inout object: T) if (object.health <= 0)  object.setAlive(false)  

We controleren het object Gezondheid eigendom. Als we minder dan of gelijk aan nul zijn, bellen we setAlive (_ :) op het object, binnenkomend vals. Omdat T is vereist om te voldoen aan de Gezond protocol, we weten dat het setAlive (_ :) functie kan worden aangeroepen op elk object dat wordt doorgegeven aan de controleren(_:) functie.

5. Geassocieerde typen

Als u meer controle wilt over uw protocollen, kunt u bijbehorende typen gebruiken. Laten we het voorbeeld van de binaire boom opnieuw bekijken. We willen een functie maken om bewerkingen in een binaire structuur uit te voeren. We hebben een manier nodig om ervoor te zorgen dat het invoerargument voldoet aan wat we definiëren als een binaire boom. Om dit op te lossen, kunnen we een BinaryTree protocol.

protocol BinaryTree typealias dataType mutating func insert (data: dataType) func index (i: Int) -> dataType var data: dataType get 

Dit gebruikt een geassocieerd type typealias dataType. data type is vergelijkbaar met een generiek. T van eerder, gedraagt ​​zich op dezelfde manier als data type. We specificeren dat een binaire boom de functies moet implementeren invoegen (_ :) en inhoudsopgave(_:)invoegen (_ :) accepteert een argument van het type data type. inhoudsopgave(_:) geeft a terug data type voorwerp. We specificeren ook dat de binaire structuur h moet zijnave een eigendom gegevens dat is van het type data type.

Dankzij ons geassocieerd type weten we dat onze binaire structuur consistent zal zijn. We kunnen aannemen dat het type is doorgegeven aan invoegen (_ :), gegeven door inhoudsopgave(_:), en gehouden door gegevens is hetzelfde voor elk. Als de typen niet allemaal hetzelfde waren, kwamen we problemen tegen.

6. Waar Clausule

Met Swift kunt u ook gebruiken waar clausules met generieken. Laten we kijken hoe dat werkt. Er zijn twee dingen waar clausules ons in staat stellen om te bereiken met generieke geneesmiddelen:

  • We kunnen afdwingen dat gekoppelde typen of variabelen binnen een protocol van hetzelfde type zijn.
  • We kunnen een protocol toewijzen aan een bijbehorend type.

Om dit in actie te laten zien, laten we een functie implementeren om binaire bomen te manipuleren. Het doel is om de maximale waarde tussen twee binaire bomen te vinden.

Voor de eenvoud zullen we een functie toevoegen aan de BinaryTree protocol genoemd in volgorde(). In volgorde is een van de drie populaire diepte-eerste traversal types. Het is een ordening van de knooppunten van de boom die recursief reist, linkerboom, huidige knoop, rechterboom.

protocol BinaryTree typealias dataType mutating func insert (data: dataType) func index (i: Int) -> dataType var data: dataType get // NEW func inorder () -> [dataType]

We verwachten het in volgorde() functie om een ​​array met objecten van het bijbehorende type te retourneren. We voeren ook de functie uit twoMax (treeOne: treeTwo :)die twee binaire bomen accepteert.

func twoMax (inout treeOne: B, inout treeTwo: T) -> B.dataType var inorderOne = treeOne.inorder () var inorderTwo = treeTwo.inorder () if (inorderOne [inorderOne.count]> inorderTwo [inorderTwo.count])  return inorderOne [inorderOne.count] else return inorderTwo [inorderTwo.count]

Onze verklaring is behoorlijk lang vanwege de waar clausule. De eerste vereiste, B.dataType == T.dataType, stelt dat de bijbehorende typen van de twee binaire bomen hetzelfde moeten zijn. Dit betekent dat hun gegevens objecten moeten van hetzelfde type zijn.

De tweede reeks vereisten, B.dataType: Comparable, T.dataType: Comparable, stelt dat de bijbehorende typen van beide moeten voldoen aan de Vergelijkbaar protocol. Op deze manier kunnen we controleren wat de maximale waarde is bij het uitvoeren van een vergelijking.

Interessant is dat, vanwege de aard van een binaire boom, we weten dat het laatste element van een in volgorde zal het maximale element in die boom zijn. Dit komt omdat in een binaire structuur de meest rechtse knoop de grootste is. We hoeven alleen naar die twee elementen te kijken om de maximale waarde te bepalen.

We hebben drie gevallen:

  1. Als boomstructuur één de maximumwaarde bevat, is het laatste element van de laatste in de rangorde het grootst en retourneren we het in de eerste als uitspraak.
  2. Als boomstructuur twee de maximumwaarde bevat, dan is het laatste element van deze laatste het grootst en retourneren we het in de anders clausule van de eerste als uitspraak.
  3. Als hun maxima gelijk zijn, dan geven we het laatste element in boomstructuur 2 terug, wat nog steeds het maximum is voor beide.

Conclusie

In deze zelfstudie hebben we ons gericht op generieke geneesmiddelen in Swift. We hebben geleerd over de waarde van generieke geneesmiddelen en onderzocht hoe generieken kunnen worden gebruikt in functies, klassen en structuren. We hebben ook gebruik gemaakt van generieke geneesmiddelen in protocollen en onderzochte geassocieerde typen en waar clausules.

Met een goed begrip van generieke geneesmiddelen kunt u nu meer veelzijdige code maken en kunt u beter omgaan met moeilijke coderingsproblemen.