talen die eersteklas functies ondersteunen, stellen u in staat om functies en methoden te gebruiken, net als elk ander object of waarde. U kunt ze doorgeven als argumenten, opslaan in Eigenschappen of ze retourneren vanuit een andere functie. In volgorde woorden, de taal behandelt functies als “first class citizens”.

hoewel Swift niet de eerste taal is die deze manier van omgaan met functies ondersteunt, is het normaal gesproken een functie die je ziet in meer dynamische talen zoals JavaScript of Lua. Dus het combineren van Swift ‘ s robuuste statische type systeem met eersteklas functies wordt een heel interessante combinatie, en kan ons in staat stellen om een aantal mooie creatieve dingen te doen 😀.

deze week, laten we eens een kijkje nemen op een paar verschillende manieren waarop eersteklas functies kunnen worden gebruikt in Swift!

Instabug: Los bugs, crashes en andere problemen veel sneller met behulp van de gedetailleerde stack traces, netwerk logs en UI gebeurtenissen die Instabug automatisch hecht aan elk bugrapport. Gebruikt door mij en duizenden iOS-ontwikkelingsteams over de hele wereld. Probeer het gratis en integreer het met slechts een enkele regel code.

functies doorgeven als argumenten

laten we beginnen met de basis. Omdat functies als waarden kunnen worden gebruikt, betekent dit dat we ze als argumenten kunnen doorgeven. Bijvoorbeeld, laten we zeggen dat we een array van subviews willen toevoegen aan een weergave. Normaal gesproken doen we zoiets als dit:

let subviews = subviews.forEach { subview in view.addSubview(subview)}

de bovenstaande code werkt, en er is niets mis mee. Maar als we gebruik maken van eersteklas functies, kunnen we eigenlijk de verbositeit ervan heel veel verminderen.

wat we kunnen doen is de addSubview methode behandelen als een afsluiting van type (UIView) -> Void (omdat het een view accepteert om toe te voegen, en niets retourneert). Dit komt perfect overeen met het type argument dat forEach accepteert (een afsluiting van type (Element) -> Void, en in dit geval is het Element type UIView). Het resultaat is dat we view.addSubview direct als argument kunnen doorgeven aan onze forEach aanroep, zoals dit:

subviews.forEach(view.addSubview)

dat is best gaaf! 😎 Echter, een ding om in gedachten te houden, is dat bij het gebruik van de instantie methoden als sluitingen als deze U automatisch behoud van de instantie, zolang u het behoud van de sluiting. Dit is helemaal geen probleem bij het passeren van een functie als een niet-ontsnappend sluitingsargument zoals hierboven, maar voor het ontsnappen van sluitingen is het iets om je bewust van te zijn om te voorkomen dat cycli behouden.

voor meer informatie over escaping vs non-escaping sluiting argumenten en het vastleggen, check out “capturing objects in Swift closures”.

initializers doorgeven als argumenten

het leuke is dat het niet alleen functies en methoden zijn die gebruikt kunnen worden als eersteklas functies in Swift – je kunt ook initializers op deze manier gebruiken.

bijvoorbeeld, laten we zeggen dat we een reeks afbeeldingen hebben waarvoor we afbeeldingsweergaven willen maken, en dat we elk van deze afbeeldingsweergaven willen toevoegen aan een stackweergave. Met behulp van eersteklas functies kunnen we al het bovenstaande bereiken met behulp van een eenvoudige keten van map en forEach:

images.map(UIImageView.init) .forEach(stackView.addArrangedSubview)

wat ik leuk vind aan het structureren van code op deze manier is dat het erg declaratief wordt. In plaats van geneste for lussen verklaren we gewoon wat we willen dat de uitkomst is. Er is natuurlijk een balans te vinden tussen declaratieve, compacte code en leesbaarheid, maar voor eenvoudige bewerkingen zoals hierboven denk ik dat het gebruik van eersteklas functies super leuk kan zijn.

u kunt meer voorbeelden en use cases vinden voor het passeren van initializers als sluitingen in ” Simple Swift dependency injection with functions “en”Time traveling in Swift unit tests”.

verwijzingen naar instantiemethoden aanmaken

laten we wat dieper ingaan op de wondere wereld van eersteklas functies 😉. Een ding dat was puzzelen me voor de langste tijd was het feit dat ik kreeg instantie methode automatische voltooiing suggesties toen ik wilde een statische methode te bellen. Probeer UIView. in Xcode te typen om te zien wat ik bedoel, je krijgt elke instantie methode als een suggestie 🤔.

eerst dacht ik dat dit een Xcode bug was, maar toen besloot ik het te onderzoeken. Het blijkt dat voor elke instantie methode een type heeft, is er een overeenkomstige statische methode waarmee je die instantie methode op te halen als een afsluiting, door het doorgeven van een instantie als een argument.

bijvoorbeeld, we kunnen het volgende gebruiken om een verwijzing naar de removeFromSuperview methode op te halen voor een gegeven UIView instantie:

let closure = UIView.removeFromSuperview(view)

het aanroepen van de bovenstaande afsluiting zou precies hetzelfde zijn als het aanroepen van view.removeFromSuperview(), wat interessant is, maar is het echt nuttig? Laten we eens kijken naar een paar scenario ‘ s waar het gebruik van deze functie kan eigenlijk leiden tot een aantal mooie coole resultaten.

XCTest op Linux

een manier waarop een van Apple ‘ s frameworks deze functie gebruikt, is bij het uitvoeren van tests met XCTest op Linux. Op Apple ‘ s eigen platforms werkt XCTest met behulp van de Objective-C runtime om alle testmethoden voor een bepaalde testcase op te zoeken en deze vervolgens automatisch uit te voeren. Echter, op Linux is er geen Objective-C runtime, dus het vereist dat we een beetje boilerplate schrijven om onze tests uit te voeren.

eerst moeten we een statisch allTests woordenboek declareren dat een mapping bevat tussen onze testnamen en de te draaien methoden:

we geven het bovenstaande woordenboek door aan de functie XCTMain om onze tests uit te voeren:

XCTMain()

onder de motorkap gebruikt dit de mogelijkheid om instantiemethoden te extraheren met behulp van zijn statische equivalent, wat ons in staat stelt om eenvoudig naar de functies te verwijzen bij naam in een statische context, terwijl het framework nog steeds Instant is om instantiemethoden te genereren om uit te voeren. Heel slim! 👍

zonder deze functie hadden we zoiets als dit moeten schrijven:

een instantie methode aanroepen op elk element in een reeks

laten we deze functie zelf gebruiken voor een spin. Net zoals we de instantiemethode van een ander object als argument konden doorgeven aan forEach, zou het niet cool zijn als we ook een instantiemethode konden doorgeven waarvan we willen dat elk element in een reeks wordt uitgevoerd?

bijvoorbeeld, laten we zeggen dat we een reeks subviews hebben die we uit hun superview willen verwijderen. In plaats van dit te moeten doen:

for view in views { view.removeFromSuperview()}

zou het niet cool zijn als we dit konden doen in plaats daarvan:

views.forEach(UIView.removeFromSuperview)

het goede nieuws is dat we kunnen, alles wat we hoeven te doen is een kleine extensie te maken op Sequence die een van deze statisch verwezen instantie methoden accepteert. Omdat het functies zijn die een functie genereren (Functionceptie! 😂 ) hun type zal altijd (Type) -> (Input) -> Output zijn, dus voor onze extensie kunnen we een forEach overload creëren die een sluiting van een dergelijk type accepteert:

we kunnen nu eenvoudig instance methoden aanroepen op elk lid van een reeks! 🎉

uitvoering van doelstelling / actie zonder doelstelling-C

laten we nog een voorbeeld bekijken. In UIKit, het doel / actie patroon is heel gebruikelijk, voor alles van het observeren van de knop klikken om te reageren op gebaren. Ik persoonlijk hou echt van dit patroon, omdat het laat ons gemakkelijk gebruik maken van een instantie methode als een callback zonder zorgen te maken over de behouden cyclus probleem dat we eerder besproken (bij het verwijzen naar een instantie methode als een sluiting).

de manier waarop target/action wordt geïmplementeerd in UIKit is echter afhankelijk van Objective-C selectors (daarom moet je private actiemethoden annoteren met @objc). Laten we zeggen dat we het doel/actiepatroon wilden toevoegen aan een van onze aangepaste weergaven, en laten we zeggen dat we het willen doen zonder te vertrouwen op Objective-C selectors. Dat klinkt misschien als een hoop werk en dat het dingen erg ingewikkeld zal maken, maar dankzij eersteklas functies-het is heel eenvoudig! 😀

laten we beginnen met het definiëren van een Action typealias als een statische functie die een instantie methode retourneert voor een bepaald type en invoer:

typealias Action<Type, Input> = (Type) -> (Input) -> Void

laten we vervolgens onze visie creëren. We maken een ColorPicker die de gebruiker een kleur laat kiezen in een teken-app, en voegen een methode toe voor het toevoegen van een doel & actie. We zullen alle observaties als sluitingen bijhouden, en elke keer dat een afsluiting wordt uitgevoerd, genereren we een instance methode voor het gegeven doel en draaien het als volgt:

het leuke is dat we eigenlijk eersteklas functies nog meer hierboven kunnen gebruiken. Door de map API op Optional te gebruiken, kunnen we de instance methode genereren en het in één keer aanroepen, zoals dit:

observations.append { view in target.map(action)?(view)}

tot slot, laten we onze nieuwe target/action API gebruiken in een CanvasViewController, die onze ColorPickerzal presenteren. Net zoals we een target & actie zouden toevoegen aan een UIButton of UIGestureRecognizer, kunnen we gewoon de view controller zelf en een instance methode doorgeven om uit te voeren, als volgt:

Type safe targets & acties zonder enige Objective-C selectors of risico ‘ s voor geheugenlekken, gebruikmakend van slechts een paar regels code – vrij cool! 👍

ondersteun Swift by Sundell door deze sponsor uit te checken:

Instabug: Los bugs, crashes en andere problemen veel sneller met behulp van de gedetailleerde stack traces, netwerk logs en UI gebeurtenissen die Instabug automatisch hecht aan elk bugrapport. Gebruikt door mij en duizenden iOS-ontwikkelingsteams over de hele wereld. Probeer het gratis en integreer het met slechts een enkele regel code.

conclusie

eersteklas functies zijn een zeer krachtige functie. Door functies en methoden op een veel dynamischer manier te kunnen gebruiken, kunnen we een aantal interessante resultaten bereiken, en het kan echt nuttig zijn bij het implementeren van bepaalde soorten API ‘ s.

echter, in de beroemde woorden van Uncle Ben; met grote macht komt grote verantwoordelijkheid. Hoewel ik denk dat het super handig is om te leren over dit soort functies en hoe ze werken, is het ook belangrijk om wat terughoudendheid te betrachten bij het gebruik ervan. Ons doel moet altijd zijn om API ‘ s te maken die mooi en makkelijk te gebruiken zijn, en om code te schrijven die zowel gemakkelijk te lezen is & onderhouden. Eersteklas functies kunnen ons zeker helpen dat doel te bereiken, maar als het te ver gaat, kan het ook leiden tot het tegenovergestelde. Zoals altijd, mijn aanbeveling is om te experimenteren, probeer deze functies uit en zie voor jezelf of en hoe ze kunnen worden gebruikt in uw eigen code.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.