I Swift kan vi hantera funktionalitet som beror på operativsystemets version genom att använda tillgänglighets- och otillgänglighetsattribut. Dessa attribut gör det möjligt att köra specifik kod beroende på vilken version av plattformen användaren kör. Till exempel, om vi vill att vår applikation ska fungera både på den senaste versionen av iOS och på tidigare versioner, kan vi använda olika mekanismer för att kontrollera detta.
Innan Swift introducerade otillgänglighetsattributet, använde vi kod som denna:
Denna kod användes för att säkerställa att funktionaliteten i vår applikation skulle fungera på äldre versioner av operativsystemet. Med otillgänglighetsattributet kan vi dock skriva koden på ett mer läsbart sätt:
En viktig skillnad mellan tillgänglighets- och otillgänglighetsattributen är att plattformen wildcard * inte är tillåtet med otillgänglighetsattributet. Syftet med detta är att undvika osäkerhet om vilka plattformar som ska betraktas som otillgängliga. Otillgänglighetsattributet kontrollerar endast de specifika plattformar som anges i listan. Här är ett exempel på hur vi kan använda otillgänglighetsattributet för flera plattformar:
Med hjälp av otillgänglighetsattributet blir koden både mer läsbar och lättare att underhålla, särskilt när det handlar om plattformsspecifik logik. Denna mekanism förenklar också felsökning och gör det lättare att förstå var och varför viss funktionalitet är otillgänglig.
Utöver de grundläggande funktionerna i Swift för att hantera tillgänglighet och otillgänglighet, finns det flera viktiga aspekter att överväga. För det första, även om attributet #unavailable gör koden mer elegant, är det avgörande att vara noga med vilka versioner och plattformar som stöds och varför. I vissa fall kan det vara mer lämpligt att använda #available om vi behöver specifika funktioner från nyare versioner, medan #unavailable bör användas när en äldre version inte längre stöds. Det är också viktigt att överväga hur vi kommunicerar för dessa versioner till användaren, särskilt om en viss funktionalitet är otillgänglig.
Dessutom kan det vara bra att tänka på hur man hanterar fallback-lösningar när äldre plattformar inte har vissa funktioner. Här är det ofta nödvändigt att utveckla alternativa lösningar som erbjuder liknande funktionalitet, även om den inte är exakt densamma. Denna strategi kan hjälpa till att skapa en bättre användarupplevelse, särskilt när användare på äldre system förväntar sig en smidig och fungerande app trots tekniska begränsningar.
Hur skapar man och använder anpassade subscript i Swift?
I Swift används subscripts för att ge snabb åtkomst till element i samlingstyper som arrayer, ordböcker och andra typer som vi själva definierar. Subscripts är ett kraftfullt verktyg som tillåter oss att läsa och skriva till objekt på ett flexibelt sätt, vilket gör dem användbara när vi vill dölja detaljer om hur data lagras eller när vi vill tillåta en mer intuitiv syntax för användare av våra typer.
För att lägga till ett element i slutet av en array eller för att få antalet objekt i en array, kan vi använda funktioner eller egenskaper, som i följande exempel:
När vi skapar anpassade typer i Swift kan vi använda subscripts för att arbeta med interna datalager som en array eller en ordbok utan att exponera lagringsmetoden för externa användare. På detta sätt kan vi göra våra objekt mer flexibla och enklare att förändra utan att påverka den externa koden. Här är ett exempel på hur man skapar en subscript för att läsa och skriva till ett backend-array:
I detta exempel har vi en klass MyNames som har en intern array, och genom att använda en subscript kan vi läsa och skriva till denna array utan att behöva exponera den direkt. På så sätt förhindras användare av klassen att få åtkomst till arrayen direkt, vilket gör det enklare att byta lagringsmetod i framtiden (t.ex. från en array till en ordbok eller en databas). Dessutom gör subscripts det möjligt att lägga till validering av indata innan de sätts in i den interna strukturen, vilket säkerställer att data hålls korrekt.
För att skapa en instans av MyNames och använda subscripten kan vi göra så här:
I detta exempel ändrar vi värdet vid index 0 i arrayen. Istället för att ge direkt åtkomst till arrayen, kan vi genom subscripten utföra operationer på data och samtidigt dölja hur den lagras.
Läs- och skrivbara subscripts
En subscript i Swift kan vara både läs- och skrivbar, beroende på om vi deklarerar en setter eller ej. Om vi inte vill tillåta externa användare att ändra ett värde, kan vi skapa en read-only subscript. Här är ett exempel på en sådan subscript:
I det här fallet behöver vi inte deklarera någon getter eller setter, eftersom Swift automatiskt gör subscripten read-only. Vi kan också uttryckligen deklarera en getter för att skapa en läsbar subscript:
I båda fallen är subscripten läsbar, men inte skrivbar. Det är viktigt att förstå att Swift inte tillåter write-only subscripts, så om du vill att en subscript bara ska vara skrivbar, kan du helt enkelt inte definiera en getter.
Beräknade subscripts
Förutom att bara returnera lagrade värden, kan subscripts också användas på liknande sätt som beräknade egenskaper. Här är ett exempel där vi använder en subscript för att beräkna ett värde:
I detta exempel använder vi en subscript för att multiplicera ett tal (num) med indexet som anges vid anrop. Här är hur man använder den:
Detta är ett exempel på en beräknad subscript, där värdet inte hämtas från någon datalager, utan snarare beräknas dynamiskt baserat på en annan parameter.
Användning av String som subscript-värde
Subscripts behöver inte alltid använda hela datatyper som Int; vi kan också använda andra typer, som String, som både input och output för subscripten. Här är ett exempel:
Användningen är enkel, och resultatet ger ett meddelande baserat på inmatningen:
Statiska subscripts
Liksom statiska egenskaper och metoder, kan subscripts också vara statiska. Detta innebär att vi kan använda subscripten utan att skapa en instans av typen. Här är ett exempel på en statisk subscript:
Här kan vi använda subscript utan att skapa ett objekt av Hello:
Externa namn för subscripts
När vi definierar flera subscripts med samma parametrar, kan vi använda externa namn för att särskilja dem. Detta fungerar på samma sätt som externa namn för funktionens parametrar. Här är ett exempel där vi definierar två subscripts som tar ett heltal, men en gör en multiplikation och den andra en addition:
Med dessa externa namn, kan vi nu välja vilken subscript som ska användas beroende på vad vi vill göra (multiplicera eller addera).
Det är viktigt att förstå att subscripts är ett sätt att göra våra objekt mer användarvänliga och flexibla, men samtidigt hålla implementeringen av datalagring och manipulation inuti själva objektet. Genom att använda subscripts kan vi förbättra kodens struktur och underlätta för framtida ändringar utan att bryta befintlig funktionalitet.
Hur man använder #expect(processExitsWith:) och andra byggblock i Swift Testing
I Swift Testing kan vi använda makrot #expect(processExitsWith:) för att köra kod i en dedikerad subprocess. Detta isolerar den från huvudtestaren och möjliggör att vi kan bedöma hur processen avslutas. När vi använder detta makro kan vi specificera att processen ska avslutas antingen med .success (normal avslutning) eller .failure (krasch eller assertion-fel). För att detta ska fungera måste testfunktionen vara markerad som asynkron, och #expect-blocket måste vänta på resultatet. Detta gör det möjligt för testet att suspendera sig själv medan Swift lanserar och övervakar subprocessen i bakgrunden.
För att förstå hur detta fungerar kan vi titta på ett exempel där vi testar en division med noll. Här definieras två heltal, där nämnaren är ett slumpmässigt tal mellan 0 och 1. En precondition säkerställer att division med noll inte är tillåten. Om denna förutsättning misslyckas, kommer Swift att utlösa ett "trap"-fel som normalt skulle krascha testköraren. Genom att använda #expect(processExitsWith: .failure) kan Swift nu köra koden i en subprocess och bekräfta att det verkligen misslyckas på det förväntade sättet. Om förutsättningen tas bort eller ändras så att divisionen genomförs, kommer testet att misslyckas, men inte på grund av en krasch utan eftersom processen inte avslutades med den förväntade "failure"-statusen.
Det finns några viktiga saker att tänka på när man använder exit-tester:
-
Endast ett #expect(processExitsWith:) anrop är tillåtet per test.
-
Subprocesser delar inte tillstånd med huvudtestaren – globala variabler, mocks eller sidoeffekter påverkar inte varandra.
-
Tester som förväntar sig krascher måste alltid använda await, eftersom subprocesshantering är asynkron.
När vi har förstått hur exit-tester fungerar, kan vi gå vidare till nästa byggblock i Swift Testing: Traits.
Traits är en användbar funktion som gör det möjligt att lägga till detaljerad metadata och kontrollera villkoren under vilka tester ska köras. Genom att lägga till metadata till våra tester kan vi inkludera information som visningsnamn, referens till buggrapport eller andra relevanta uppgifter som förbättrar vår dokumentation och gör det enklare att förstå syftet och kontexten för varje test. Traits gör det också möjligt att köra tester konditionellt genom att specificera villkor under vilka ett test ska köras. Till exempel kan vi aktivera ett test att köra endast om en viss funktion är aktiverad eller när testet körs i en specifik miljö.
En annan användbar funktion är att traits kan användas för att organisera våra tester genom att tilldela taggar. Taggar gör det enklare att filtrera och köra specifika tester i Xcode, vilket förenklar testhantering. Dessutom förenklar de skapandet av parameteriserade tester, där samma test kan köras flera gånger med olika parametrar. Argumenten kan definieras utanför testet, så att varje test som körs visas i Xcode sidofält och ger möjlighet att köra testerna med specifika argument vid behov.
Här är några exempel på hur traits kan användas:
När vi nu har sett hur traits används, kan vi gå vidare till nästa byggblock, nämligen testsviter.
Sviter kan skapas genom att infoga tester i en struktur, och varje struktur som innehåller @Test-funktioner anses automatiskt vara en svit. Vi kan också skapa en svit genom att använda @Suite-annotation. Sviter kan nästlas in i andra sviter, vilket skapar en hierarkisk struktur som gör det lättare att organisera tester. Sviter är särskilt användbara när man hanterar många tester, och de kan vara strukturer, aktörer eller klasser – men strukturer rekommenderas på grund av deras värdesemantik. Traits kan tillämpas på sviter, vilket gör att alla tester inom sviten ärver dessa egenskaper. Till exempel, om vi applicerar en tagg på en svit, kommer denna tagg att gälla för alla tester inom den sviten.
Här är exempel på hur man definierar sviter:
Båda exemplen är sviter eftersom varje struktur som innehåller @Test-funktioner automatiskt betraktas som en svit.
För att bättre förstå användningen av Swift Testing kan vi titta på ett praktiskt exempel: att skapa en räknarapp. Vid skapandet av ett nytt projekt med Swift Testing genereras även UI-tester med XCTest. En enkel räknare är ett bra exempel för att demonstrera testkoncept eftersom det involverar grundläggande aritmetiska operationer som lätt kan verifieras med grundläggande tester.
I vårt exempel skapar vi en räknareapp där backend-koden för räknaren definieras i en struktur:
När vi skapar räknarenappen med Swift Testing genereras tre moduler: en för själva applikationen, en för enhetstester och en för UI-tester. För att enhetstesterna ska kunna komma åt den interna koden i appens modul, används @testable-attributet, vilket gör att enhetstester kan få åtkomst till interna komponenter i applikationen som normalt inte skulle vara synliga utanför modulen.
För att möjliggöra detta måste vi aktivera testbarhetsinställningen genom att sätta "Enable Testability" till "Yes" för applikationens mål i Xcode.
Hur protokoll och deras sammansättning skapar flexibel och återanvändbar kod i protokollorienterad programmering
Protokollorienterad programmering är en kraftfull metod för att bygga flexibla och modulära system där huvudfokus ligger på användning av protokoll för att definiera metoder, egenskaper och krav för typer. Denna metod främjar återanvändning av komponenter och gör det möjligt att bygga komplex funktionalitet genom att kombinera små, specifika protokoll. En av de mest användbara funktionerna i protokollorienterad design är protokollkomposition, där en typ kan konformera sig till flera protokoll och på så sätt ärva krav och beteenden från varje.
Exemplet nedan illustrerar användning av where-villkoret för att filtrera genom for-loopens resultat och endast hämta instanser som uppfyller specifika kriterier, i detta fall protokollet LandVehicle. Genom att använda detta villkor kan vi hantera och interagera med instanser av LandVehicle och använda de funktioner som tillhandahålls av protokollet.
I detta exempel används where för att filtrera objekten så att endast de som är instanser av LandVehicle behandlas vidare. Vi kan sedan kasta dessa objekt som LandVehicle och använda de metoder som protokollet definierar. Denna form av typcastning ger oss möjlighet att nyttja metoder som är specifika för protokollet utan att behöva skriva om kod för varje enskild typ av fordon.
Protokollärvning och sammansättning är grundläggande för att skapa högkvalitativ och lättunderhållen kod. Protokollärvning gör det möjligt för ett protokoll att ärva krav från andra protokoll. Till skillnad från klassarv, där en klass bara kan ärva från en annan klass, tillåter protokollärvning att ett protokoll ärver från flera andra protokoll, vilket leder till en flexibel och modulär struktur. Genom att använda protokollförlängningar kan vi också ge standardimplementeringar för metoder och egenskaper som definieras av ett protokoll, vilket minskar behovet av upprepning av kod och gör det lättare att underhålla.
Protokollkomposition är en annan viktig aspekt av protokollorienterad design. Genom att använda komposition kan vi skapa en typ som konformerar till flera protokoll och på så sätt sammansätta deras krav i ett gemensamt gränssnitt. Detta är särskilt användbart när vi har små, specialiserade protokoll som vi vill kombinera för att bygga mer komplexa strukturer utan att behöva skapa stora, tunga klasser. Ett exempel på detta kan ses när vi definierar två protokoll, Nameable och Contactable, som används för att definiera namn och kontaktinformation för en person:
Dessa protokoll kan sedan användas som bas för att skapa ett Person-protokoll som ärver deras krav:
Genom att skapa små och fokuserade protokoll som dessa kan vi bygga en modulär uppsättning komponenter som kan återanvändas på olika sätt. Vi kan exempelvis skapa ett Pet-protokoll som återanvänder Nameable för att definiera egenskaper för djur:
För att ytterligare förbättra funktionaliteten kan vi använda protokollförlängningar för att tillhandahålla standardimplementeringar av metoder och beräkningar. Till exempel kan vi skapa en förlängning av Person-protokollet för att automatiskt beräkna en persons ålder baserat på deras födelsedatum:
Denna metod innebär att alla typer som konformerar till Person (eller ett protokoll som ärver från Person) automatiskt får denna funktionalitet utan att behöva skriva om koden.
Protokollorienterad design erbjuder ett mångsidigt och kraftfullt sätt att bygga system som är både flexibla och lätta att underhålla. Genom att använda protokollkomposition, ärvning och förlängningar kan vi skapa kod som är modulär och återanvändbar, vilket leder till en långsiktigt hållbar och effektiv kodbas.
Jak efektivně ovládat navigaci a přiblížení obrazu v Adobe Photoshopu?
Jakým způsobem jsou japonské obchody a zaměstnání propojené s každodenní kulturou a tradicemi?
Jakým způsobem první vědci formovali naše chápání světa?
Jak používat dialogy, upozornění a notifikace v Android aplikacích

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский