Om du har arbetat med block i C eller Objective-C eller lambdas i Ruby, kommer du inte ha svårt att kasta huvudet kring konceptet stängningar. Stängningar är inget annat än funktionsblock som du kan passera i din kod.
Faktum är att vi redan har arbetat med nedläggningar under tidigare lektioner. Det är rätt: funktioner är stängningar också. Låt oss börja med grunderna och inspektera anatomin av en stängning.
Som sagt, en nedläggning är ett funktionsblock som du kan passera i din kod. Du kan skicka en stängning som ett argument för en funktion eller du kan lagra den som en egenskap av ett objekt. Stängningar har många användningsfall.
Namnet stängning tips på en av de viktigaste egenskaperna hos nedläggningar. En stängning fångar variablerna och konstanterna i det sammanhang där den definieras. Detta kallas ibland som stänger över dessa variabler och konstanter. Vi kommer att titta på värdesupptagning mer i detalj i slutet av den här lektionen.
Du har redan lärt dig att funktioner kan vara otroligt kraftfulla och flexibla. Eftersom funktioner är stängningar är stängningarna lika flexibla. I den här artikeln upptäcker du hur flexibla och kraftfulla de är.
C-programmeringsspråket har ett liknande begrepp, block. Stängningar i Swift har emellertid några fördelar. En av de viktigaste fördelarna med stängningar i Swift är att minneshantering är något du, utvecklaren, inte behöver oroa sig för.
Även behåller cykler, som inte är ovanliga i C eller Objective-C, hanteras av Swift. Detta minskar svårlästa minnesläckor eller kraschar som orsakas av ogiltiga pekare.
Den grundläggande syntaxen för en nedläggning är inte svår, och det kan påminna dig om globala och kapslade funktioner som vi tidigare behandlade i denna serie. Ta en titt på följande exempel.
(a: Int) -> Int till gengäld a + 1
Det första du märker är att hela förslutningen är inslaget i ett par lockiga hängslen. Parametrarna för förslutningen är inslagna i ett par parenteser, separerade från returtypen av ->
symbol. Ovanstående stängning accepterar ett argument, en
, av typ int
, och returnerar en int
. Luckans kropp börjar efter i
nyckelord.
Namngivna stängningar, det vill säga globala och kapslade funktioner, ser lite annorlunda ut. Följande exempel ska illustrera skillnaderna.
func increment (_a: Int) -> Int return a + 1
De mest framträdande skillnaderna är användningen av func
sökord och positionen för parametrarna och returtypen. En stängning startar och slutar med en lockig spännbom, omsluter parametrarna, returtypen och stängningskroppen. Trots dessa skillnader, kom ihåg att varje funktion är en nedläggning. Inte alla stängningar är dock en funktion.
Stängningar är kraftfulla, och följande exempel illustrerar hur användbara de kan vara. I exemplet skapar vi en rad stater. Vi åberopar Karta(_:)
funktion på matrisen för att skapa en ny matris som bara innehåller de två första bokstäverna i varje stat som en aktiverad sträng.
var states = ["California", "New York", "Texas", "Alaska"] förkortasStates = states.map ((state: String) -> String i let index = state.index (state.startIndex, offsetBy : 2) returnera state.substring (till: index) .uppercased ()) print (förkortadeStates)
De Karta(_:)
funktion eller metod är vanligt för många programmeringsspråk och bibliotek, till exempel Ruby, PHP och JavaScript. I ovanstående exempel Karta(_:)
funktionen åberopas på stater
array, omvandlar dess innehåll och returnerar en ny array som innehåller de transformerade värdena. Oroa dig inte för kroppens stängning för nu.
Tidigare i denna serie lärde vi oss att Swift är ganska smart. Låt mig visa dig exakt hur smart. Ställningen av stater är en rad strängar. Eftersom vi åberopar Karta(_:)
funktionen på matrisen vet Swift att stat
argumentet är av typ Sträng
. Det betyder att vi kan utelämna typen, som visas i det uppdaterade exemplet nedan.
låt förkortasStates = states.map (state) -> String i let index = state.index (state.startIndex, offsetBy: 2) returnera state.substring (till: index) .uppercased ())
Det finns några fler saker som vi kan utelämna från ovanstående exempel, vilket resulterar i följande en-liner.
låt förkortasStates = states.map (state in state.substring (till: state.index (state.startIndex, offsetBy: 2)). uppercased ())
Låt mig förklara vad som händer.
Kompilatorn kan dra slutsatsen att vi returnerar en sträng från den stängning som vi passerar till Karta(_:)
funktion, vilket innebär att det inte finns någon anledning att inkludera den i definitionen för avslutningsuttryck.
Vi kan bara göra detta om stängningskroppen innehåller ett enda uttalande. I det fallet kan vi ställa detta uttalande på samma sätt som nedläggnings definitionen, som visas i ovanstående exempel. Eftersom det inte finns någon returtyp i definitionen och nr ->
symbol som ligger före returtypen, kan vi utelämna parenteserna som omsluter stängningens parametrar.
Det stannar dock inte här. Vi kan använda stenografi argument namn för att förenkla ovanstående avslutning uttryck ännu mer. När du använder ett inline-stängningsuttryck, som i exemplet ovan, kan vi utelämna listan över parametrar, inklusive i
nyckelord som skiljer parametrarna från stängningskroppen.
I stängningskroppen hänvisar vi till argumenten med hjälp av stenografi-argumentnamn som Swift ger oss med. Det första argumentet refereras av $ 0
, den andra vid $ 1
, etc.
I det uppdaterade exemplet nedan har jag utelämnat listan över parametrar och i
sökord och ersatte stat
argument i stängningens kropp med shorthand argument namnet $ 0
. Det resulterande uttalandet är mer koncist utan att äventyra läsbarheten.
låt förkortasStates = states.map ($ 0.substring (till: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ())
Det svängda programmeringsspråket definierar också ett koncept som kallas efterföljande stängningar. Tanken är enkel. Om du skickar en stängning som det senaste argumentet för en funktion kan du placera den stängningen utanför parenteserna i funktionsanropet. Följande exempel visar hur detta fungerar.
låt förkortasStates = states.map () $ 0.substring (till: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()
Om det enda argumentet i funktionssamtalet är stängningen, är det även möjligt att utelämna parenteserna i funktionsanropet.
låt förkortasStates = states.map $ 0.substring (till: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()
Observera att detta också fungerar för stängningar som innehåller flera uttalanden. Faktum är att det är den främsta anledningen till att efterföljande stängningar finns i Swift. Om en slutning är lång eller komplex och det är det sista argumentet för en funktion, är det ofta bättre att använda syntaxen för avslutande stängning.
låt förkortasStates = states.map (state) -> String i let index = state.index (state.startIndex, offsetBy: 2) returnera state.substring (till: index) .uppercased ()
När du använder stängningar, hittar du ofta dig själv att använda eller manipulera konstanter och variabler från stängningens omgivande sammanhang i kroppens stängning. Detta kallas ofta värdefångande. Det betyder helt enkelt att en nedläggning kan fånga värdena av konstanter och variabler från det sammanhang där det definieras. Ta följande exempel för att bättre förstå begreppet värdeupptagning.
func changeCase (storlek: Bool, ofStrings strängar: String ...) -> [String] var newStrings = [String] () func changeToUppercase () för s i strängar newStrings.append (s.uppercased ()) func changeToLowerCase () för s i strängar newStrings.append (s.lowercased () om versaler changeToUppercase () annars changeToLowerCase () returnera newStrings låt uppercasedStates = changeCase (storlek: true, ofStrings: "Kalifornien "," New York ") låt lowercasedStates = changeCase (storlek: false, ofStrings:" California "," New York ")
Jag är säker på att du håller med om ovanstående exempel är lite konstruerat, men det visar tydligt hur värdefångande fungerar i Swift. De kapslade funktionerna, changeToUppercase ()
och changeToLowercase ()
, har tillgång till yttre funktionens argument, stater
, så väl som newStates
variabel deklarerad i den yttre funktionen.
Låt mig förklara vad som händer.
De changeCase (versaler: ofStrings :)
funktionen accepterar en booleska som första argument och en variadisk parameter av typen Sträng
som sin andra parameter. Funktionen returnerar en rad strängar som består av strängarna som överförts till funktionen som det andra argumentet. I funktionens kropp skapar vi en muterbar grupp av strängar, newStrings
, där vi lagrar de modifierade strängarna.
De kapslade funktionerna slingrar sig över strängarna som skickas till changeCase (versaler: ofStrings :)
funktionen och ändra fallet för varje sträng. Som du kan se har de direkt tillgång till strängarna som passerade till changeCase (versaler: ofStrings :)
funktion samt newStrings
array, som deklareras i kroppen av changeCase (versaler: ofStrings :)
fungera.
Vi kontrollerar värdet av versal
, Ring till lämplig funktion och returnera newStrings
array. De två linjerna i slutet av exemplet visar hur changeCase (versaler: ofStrings :)
funktion fungerar.
Trots att jag har visat värdefångande med funktioner, kom ihåg att varje funktion är en nedläggning. Med andra ord gäller samma regler för namngivna stängningar.
Det har nämnts flera gånger i den här artikeln: funktioner är stängningar. Det finns tre sorters stängningar:
Globala funktioner, som t.ex. trycket (_: separator: terminator :)
funktionen i Swift standardbiblioteket, fånga inga värden. Nested funktioner har dock tillgång till och kan fånga värdena av konstanter och värden för den funktion de definieras i. Det tidigare exemplet illustrerar detta koncept.
Slututtryck, även kända som namngivna stängningar, kan fånga värdena på konstanter och variabler i det sammanhang de definieras i. Detta liknar väsentligen inbyggda funktioner.
En förslutning som fångar värdet på en variabel kan ändra värdet på den variabeln. Swift är smart nog att veta om den ska kopiera eller referera till värdena för konstanterna och variablerna som det fångar.
Utvecklare som lär sig Swift och har liten erfarenhet av andra programmeringsspråk kommer att ta detta beteende för givet. Det är emellertid en viktig fördel att Swift förstår hur fångade värden används i en stängning och som ett resultat kan hantera minneshantering för oss.
Stängningar är ett viktigt koncept, och du kommer att använda dem ofta i Swift. De gör att du kan skriva flexibel, dynamisk kod som är lätt både att skriva och förstå.
I nästa artikel utforskar vi objektorienterad programmering i Swift, som börjar med objekt, strukturer och klasser.
Om du vill lära dig hur du använder Swift 3 för att koda avancerade funktioner för verk i världen, kolla in vår kurs. Gå vidare med Swift: Animation, Networking och Custom Controls. Följ med Markus Mühlberger när han kodar en funktionell iOS weather app med live väder data, anpassade UI komponenter och några smarta animationer för att få allt till liv.