språk som stöder förstklassiga funktioner gör att du kan använda funktioner och metoder precis som alla andra objekt eller värde. Du kan skicka dem som argument, spara dem i egenskaper eller returnera dem från en annan funktion. I ordord fungerar språket som ”förstklassiga medborgare”.

medan Swift knappast är det första språket som stöder detta sätt att hantera funktioner, är det normalt en funktion som du ser på mer dynamiska språk som JavaScript eller Lua. Så att kombinera Swifts robusta statiska typsystem med förstklassiga funktioner blir en ganska intressant kombination, och kan göra det möjligt för oss att göra några ganska kreativa saker bisexuell.

den här veckan, låt oss ta en titt på några olika sätt som förstklassiga funktioner kan användas i Swift!

Instabug: lösa buggar, krascher och andra frågor mycket snabbare med hjälp av detaljerade stack spår, nätverksloggar och UI händelser som Instabug automatiskt fäster varje felrapport. Används både av mig och tusentals iOS-utvecklingsteam runt om i världen. Prova det gratis och integrera det med bara en enda kodrad.

passande funktioner som argument

låt oss börja med grunderna. Eftersom funktioner kan användas som värden betyder det att vi kan skicka dem som argument. Låt oss till exempel säga att vi vill lägga till en rad undervisningar i en vy. Normalt kan vi göra något så här:

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

ovanstående kod fungerar, och det är inget fel med det. Men om vi utnyttjar förstklassiga funktioner kan vi faktiskt minska dess verbositet ganska mycket.

vad vi kan göra är att behandla addSubview – metoden som en stängning av typen (UIView) -> Void (eftersom den accepterar en vy för att lägga till och inte returnerar någonting). Detta matchar perfekt den typ av argument som forEach accepterar (en stängning av typen (Element) -> Void, och i detta fall är typen Element UIView). Resultatet är att vi kan skicka view.addSubview direkt som ett argument till vårt forEach – samtal, så här:

subviews.forEach(view.addSubview)

det är ganska coolt! Men en sak att tänka på är att när du använder instansmetoder som stängningar så här behåller du automatiskt instansen så länge du behåller Stängningen. Detta är inte ett problem alls när man passerar en funktion som ett icke-flyktande stängningsargument som ovan, men för att undkomma stängningar är det något att vara medveten om för att undvika att behålla cykler.

för mer information om att fly vs icke-flyende stängningsargument och fånga, kolla in ”fånga objekt i snabba stängningar”.

passerar initializers som argument

det coola är att det inte bara är funktioner och metoder som kan användas som förstklassiga funktioner i Swift – du kan också använda initializers på detta sätt.

låt oss till exempel säga att vi har en rad bilder som vi vill skapa bildvyer för, och att vi vill lägga till var och en av dessa bildvyer i en stackvy. Med hjälp av förstklassiga funktioner kan vi uppnå alla ovanstående med en enkel kedja av map och forEach:

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

vad jag tycker om att strukturera kod på detta sätt är att det blir mycket deklarativt. Istället för kapslade for – slingor förklarar vi helt enkelt vad vi vill att resultatet ska bli. Det finns naturligtvis en balans mellan deklarativ, kompakt kod och läsbarhet, men för enkla operationer som ovan tror jag att utnyttja förstklassiga funktioner kan vara super trevligt.

du kan hitta fler exempel och användningsfall för att skicka initialisatorer som stängningar i ”Simple Swift dependency injection with functions” och ”Time traveling in Swift unit tests”.

skapa instans metodreferenser

Låt oss dyka lite djupare in i den underbara världen av första klassens funktioner Xiaomi. En sak som förbryllade mig längst var det faktum att jag fick förslag på automatisk komplettering av instansmetod när jag ville ringa en statisk metod. Försök att skriva UIView. i Xcode för att se vad jag menar, du får varje instansmetod som ett förslag på bisexuell.

först trodde jag att det här var ett Xcode-fel, men då bestämde jag mig för att undersöka det. Det visar sig att för varje instansmetod en typ Har finns det en motsvarande statisk metod som låter dig hämta den instansmetoden som en stängning genom att skicka en instans som ett argument.

till exempel kan vi använda följande för att hämta en referens till removeFromSuperview – metoden för en given UIView – instans:

let closure = UIView.removeFromSuperview(view)

att ringa ovanstående stängning skulle vara exakt samma som att ringa view.removeFromSuperview(), vilket är intressant, men är det verkligen användbart? Låt oss ta en titt på några scenarier där användning av den här funktionen faktiskt kan leda till några ganska coola resultat.

XCTest på Linux

ett sätt som en av Apples ramar använder den här funktionen är när du kör tester med XCTest på Linux. På Apples egna plattformar fungerar XCTest genom att använda Objective-C runtime för att leta upp alla testmetoder för ett visst testfall och kör dem sedan automatiskt. Men på Linux finns det ingen Objective-C-runtime, så det kräver att vi skriver lite standardtext för att våra tester ska köras.

först måste vi förklara en statisk allTests ordbok som innehåller en kartläggning mellan våra testnamn och de faktiska metoderna att köra:

vi skickar sedan ovanstående ordbok till funktionen XCTMain för att köra våra test:

XCTMain()

Under huven använder detta funktionen att kunna extrahera instansmetoder med sin statiska ekvivalent, vilket gör det möjligt för oss att helt enkelt hänvisa till funktionerna med namn i ett statiskt sammanhang, samtidigt som ramverket kan generera instansmetoder att köra. Ganska smart! 6085>

utan den här funktionen skulle vi ha skrivit något så här:

ringa en instansmetod på varje element i en sekvens

Låt oss ta den här funktionen för en snurrning själva. Precis som vi kunde skicka ett annat objekts instansmetod som ett argument till forEach, skulle det inte vara coolt om vi också kunde skicka en instansmetod som vi vill att varje element i en sekvens ska utföra?

låt oss till exempel säga att vi har en rad undervisningar som vi vill ta bort från deras superview. Istället för att behöva göra detta:

for view in views { view.removeFromSuperview()}

skulle det inte vara coolt om vi kunde göra det istället:

views.forEach(UIView.removeFromSuperview)

den goda nyheten är att vi kan, allt vi behöver göra är att skapa en liten förlängning på Sequence som accepterar en av dessa statiskt refererade instansmetoder. Eftersom de är funktioner som genererar en funktion (Functionception! deras typ kommer alltid att vara (Type) -> (Input) -> Output, så för vår förlängning kan vi skapa en forEach överbelastning som accepterar en stängning av sådan typ:

vi kan nu enkelt ringa instansmetoder på varje medlem i vilken sekvens som helst!

genomförande av mål / åtgärd utan mål-C

Låt oss ta en titt på ytterligare ett exempel. I UIKit är mål – / åtgärdsmönstret mycket vanligt, för allt från att observera knappklick till att svara på gester. Jag gillar personligen verkligen det här mönstret, eftersom det låter oss enkelt använda en instansmetod som en återuppringning utan att behöva oroa sig för behållningscykelproblemet som vi diskuterade tidigare (när vi refererar till en instansmetod som en stängning).

hur target / action implementeras i UIKit är dock beroende av Objective-C-väljare (det är därför du måste kommentera privata åtgärdsmetoder med @objc). Låt oss säga att vi ville lägga till mål – / åtgärdsmönstret i en av våra anpassade vyer, och låt oss säga att vi vill göra det utan att förlita oss på Objective-C-väljare. Det kan låta som mycket arbete och att det kommer att göra saker väldigt komplicerade, men tack vare förstklassiga funktioner – det är ganska enkelt!

låt oss börja med att definiera en Action typealias som en statisk funktion som returnerar en instansmetod för en given typ och inmatning:

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

Låt oss sedan skapa vår syn. Vi skapar en ColorPicker som låter användaren välja en färg i en ritningsapp och lägga till en metod för att lägga till ett mål & – åtgärd till det. Vi håller reda på alla observationer som stängningar, och varje gång en stängning körs genererar vi en instansmetod för det givna målet och kör det, så här:

det coola är att vi faktiskt kan använda förstklassiga funktioner ännu mer ovan. Genom att använda map API på Optional kan vi generera instansmetoden och kalla den på en gång, så här:

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

slutligen, låt oss använda vår nya target/action API i en CanvasViewController, som kommer att presentera vår ColorPicker. Precis som vi skulle lägga till ett mål & – åtgärd till en UIButton eller UIGestureRecognizer, kan vi helt enkelt skicka visningskontrollen själv och en instansmetod att köra, så här:

skriv säkra mål & åtgärder utan några Objective-C – väljare eller risker för minnesläckor, med bara några rader kod-ganska coolt!

stöd Swift av Sundell genom att kolla in denna sponsor:

Instabug: lösa buggar, krascher och andra frågor mycket snabbare med hjälp av detaljerade stack spår, nätverksloggar och UI händelser som Instabug automatiskt fäster varje felrapport. Används både av mig och tusentals iOS-utvecklingsteam runt om i världen. Prova det gratis och integrera det med bara en enda kodrad.

slutsats

förstklassiga funktioner är en mycket kraftfull funktion. Genom att kunna använda funktioner och metoder på ett mycket mer dynamiskt sätt kan vi uppnå några ganska intressanta resultat, och det kan vara riktigt användbart när man implementerar vissa typer av API: er.

men i farbror Ben berömda ord; med stor makt kommer stort ansvar. Medan jag tycker att det är super användbart att lära sig om dessa typer av funktioner och hur de fungerar, är det också viktigt att utöva viss återhållsamhet när man använder dem. Vårt mål ska alltid vara att skapa API: er som är trevliga och lätta att använda, och att skriva kod som är både lättläst & underhålla. Förstklassiga funktioner kan definitivt hjälpa oss att tjäna det målet, men om det tas för långt kan det också leda till tvärtom. Som alltid är min rekommendation att experimentera, prova dessa funktioner och se själv om och hur de kan användas i din egen kod.

Lämna ett svar

Din e-postadress kommer inte publiceras.