18/11/2023
I en verden, hvor nye iPhone-modeller og iOS-versioner udgives hvert år, står app-udviklere over for en konstant udfordring: Hvordan kan man udnytte de nyeste og mest spændende funktioner, uden at ekskludere brugere med ældre enheder eller operativsystemer? Svaret ligger i Swifts kraftfulde tilgængelighedskontrol – en smart metode til at håndtere bagudkompatibilitet. Denne artikel vil dykke ned i, hvordan du kan implementere denne funktionalitet i dine apps for at sikre en stabil og moderne brugeroplevelse på tværs af alle iOS-versioner.

Forestil dig et scenarie, hvor du ønsker at implementere en banebrydende ny UI-komponent, der kun er tilgængelig i den seneste iOS-version. Uden korrekt håndtering ville din app simpelthen gå ned for brugere, der kører en ældre version af operativsystemet. Dette er præcis, hvad Swifts tilgængelighedskontrol løser. Ved at spørge, om brugeren kører en specifik eller nyere version af et operativsystem, kan vi udføre kode, kun hvis testen bestås. Dette giver os mulighed for at bruge den nyeste funktionalitet fra iOS, macOS og så videre, samtidig med at vi yder en passende 'graceful degradation' for brugere på ældre iOS-versioner.
Historisk Tilbageblik: Manuel Versionskontrol
Før Swifts indbyggede tilgængelighedskontrol var udviklere nødt til manuelt at tjekke for versionskompatibilitet. Denne tilgang var fyldt med problemer og krævede, at du husket præcis, hvornår hver komponent blev introduceret. For eksempel, hvis du ville bruge UICollectionViewCompositionalLayout i din app, men ønskede at understøtte brugere på iOS 12 og tidligere, skulle du udføre en kørselstids-kontrol af operativsystemets versionsnummer og kun bruge det nye layout, hvis det var understøttet. En typisk implementering kunne se således ud:
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 13, minorVersion: 0, patchVersion: 0)) {
print("Opret samlingsvisningen!")
}Denne metode var besværlig, især når man skulle huske introduktionstidspunkter for utallige API'er. Værre var det, hvis du overså noget kode: Hvis din app forsøgte at bruge iOS 13-kode på iOS 12, eller iOS 12-kode på iOS 11, ville den simpelthen crashe. Dette betød, at udviklere, der var ivrige efter at bruge de nyeste og bedste API'er, måtte bruge enormt meget tid på at tilføje kontroller til deres kode og sikre, at den var fejlfri.
Swifts Løsning: Automatisk API-Tilgængelighedskontrol
Helt tilbage i Swift 2 introducerede Apple API-tilgængelighedskontrol. Hvis du indstiller din apps Deployment Target til en lavere iOS-udgivelse end base-SDK'et, vil Xcode automatisk scanne hver API, du bruger, for at sikre, at den er tilgængelig i din laveste implementeringsmålversion. Disse oplysninger har været i Apples API-headers i årevis, men det er først nu, de bliver eksponeret for compileren. Det betyder, at hvis din app kompilerer, kan du være garanteret, at den ikke kalder kode, der ikke kan køre på grund af manglende API'er.
Som standard behøver du ikke gøre noget: Swift vil sammenligne din faktiske brug med dit minimum implementeringsmål, og hvis den finder en utilgængelig API, får du en fejl – og det er her arbejdet begynder.
if #available – Din Port til Nye Funktioner
For at vende tilbage til vores eksempel: Hvis du har brugt UICollectionViewCompositionalLayout, og dit implementeringsmål er sat til iOS 12.0, får du en compiler-fejl, fordi dette layout ikke er tilgængeligt før 13.0. Løsningen er at fortælle Xcode, at du ønsker, at bestemt kode kun skal udføres på iOS 13.0 eller nyere, således:
if #available(iOS 13, *) {
// Brug UICollectionViewCompositionalLayout
} else {
// Vis en ked af det-emoji eller en fallback-løsning
}I denne kode vil #available tjekke, om vi er på iOS 13 eller nyere, eller på andre ukendte platforme, der måtte blive annonceret i fremtiden – det er * i slutningen, og det er påkrævet. Og det er det: Al kode, du lægger ind i stedet for "// Brug UICollectionViewCompositionalLayout", har effektivt forhøjede rettigheder til at bruge iOS 13.0-specifik teknologi, uanset om det er klasser, metoder eller enums.

guard #available – Tidlig Exit for Elegant Kode
Hvis kode inde i en metode kun skal køres på bestemte iOS-versioner, kan du også bruge #available med guard for at producere kode som denne. Dette er især nyttigt, når du hurtigt vil afslutte en funktion, hvis de nødvendige API'er ikke er tilgængelige, hvilket fører til renere og mere læsbar kode:
guard #available(iOS 13, *) else {
return // Afslut metoden, hvis iOS 13 ikke er tilgængelig
}
// Fortsæt med iOS 13-specifik kode herKraften ved #available er, at compileren nu kan kontrollere og håndhæve API-brug på ældre operativsystemer, hvilket tidligere udelukkende var menneskeligt arbejde – det er en kæmpe forbedring, og en, der hurtigt vil vinde indpas blandt udviklere.
Markering af Hele Metoder og Klasser med @available
Som du lige har set, kan du bruge if #available til at køre versionsspecifik kode i små blokke. Men hvad nu hvis hele metoder er utilgængelige? Eller måske endda hele klasser? Swift har også dækket disse scenarier ved hjælp af @available-attributten.
@available fungerer på samme måde som #available, idet du angiver den iOS-udgivelse, du vil målrette mod, og derefter håndterer Xcode resten. For eksempel:
@available(iOS 13, *)
func useCompositionalLayout() {
// Brug UICollectionViewCompositionalLayout
}Hvis dit implementeringsmål er iOS 12, kan du ikke kalde denne useCompositionalLayout() metode uden først at foretage en tilgængelighedskontrol. Du kan stable disse kontroller, hvis du har brug for det, for eksempel:
@available(iOS 11, *)
func iOS11Work() {
// Udfør arbejde for iOS 11
if #available(iOS 12, *) {
iOS12Work()
}
}
@available(iOS 12, *)
func iOS12Work() {
// Udfør arbejde for iOS 12
if #available(iOS 13, *) {
iOS13Work()
}
}
@available(iOS 13, *)
func iOS13Work() {
// Udfør arbejde for iOS 13
}Hver gang er der effektivt en privilegieforhøjelse, så du kan bruge versionsbegrænsede API'er. For den ultimative begrænsning kan du også markere hele klasser som værende tilgængelige kun i en specifik iOS-udgivelse eller nyere – flyt blot @available-koden, hvor du ønsker den.
Der er en sidste smart funktion ved disse tilgængelighedskontroller i Swift: Du behøver ikke længere at bekymre dig om "Required" og "Optional" frameworks – compileren sorterer alt det ud for dig nu. Hurra for udviklerproduktivitet!
Forståelse af API-Tilgængelighed i Dybden (Også for Mixed-Language Projekter)
API-tilgængelighed betyder, om en specifik API (funktion, klasse, metode osv.) faktisk er tilgængelig. iOS, macOS, tvOS og watchOS håndterer API-tilgængelighed på samme måde: Når en ny API introduceres, annoteres den med en specifik makro, der angiver API'ens tilgængelighed. Denne makro udvides til annotationer, der indikerer, hvordan linker skal håndtere linkning til den, og kan give yderligere advarsler eller fejl under kompilering, når man bruger en forældet API eller forsøger at bruge en "for ny" API i en app, der er indstillet til at køre på ældre Apple OS-versioner, der mangler denne API.
For eksempel blev funktionen clock_gettime() introduceret i macOS 10.12. Dens header-deklaration ser således ud:
__CLOCK_AVAILABILITY int clock_gettime(clockid_t __clock_id, struct timespec *__tp);Hvor __CLOCK_AVAILABILITY udvides til:
__OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0)Dette fortæller os, at API'en er tilgængelig fra macOS 10.12, iOS 10.0, tvOS 10.0 og watchOS 3.0. En vigtig pointe er, at en deklaration typisk kan bruges, selv når man implementerer tilbage til en platformversion før deklarationen blev introduceret. Når dette sker, er deklarationen svagt linket (weakly linked). En svagt linket deklaration kan eller kan ikke være til stede ved køretid, og et program kan afgøre, om deklarationen er til stede, ved at kontrollere, om adressen på den deklaration er non-NULL.
Kontrol af API-Tilgængelighed ved Køretid (Runtime) i C/Objective-C
Den "gamle" måde at tjekke, om en delvist tilgængelig funktion er tilgængelig, var at tjekke dens adresse:
if (&clock_gettime == NULL) {
// clock_gettime er ikke tilgængelig!
}Ikke alene er dette lidt mærkeligt at læse, det har også nogle ulemper:
- Compileren vil stadig advare om dette.
- Objective-C-metoder og klasser kan ikke nemt kontrolleres på denne måde.
- Det er besværligt at kontrollere alle relevante symboler på denne måde.
Heldigvis er der nu en bedre måde at håndtere dette på i C/Objective-C. Compileren vil endda pege dette ud i advarslen om delvis tilgængelighed:
if (__builtin_available(macOS 10.12, *)) {
if (clock_gettime(CLOCK_REALTIME, &value) == 0) {
printf("Realtime sekunder: %ld\n", value.tv_sec);
}
} else {
// clock_gettime ikke tilgængelig!
return 1;
}Du kan kontrollere flere platformversioner blot ved at liste dem alle. Stjernen (*) i slutningen er obligatorisk og betyder "alle andre platforme". Så den tidligere kontrol, der kun listede macOS, ville stadig kompilere for iOS og crashe ved køretid, når den køres på iOS-versioner lavere end iOS 10, som mangler clock_gettime. Så sørg for at dække alle tilfælde, hvor koden vil køre i din tilgængelighedskontrol!
I Objective-C er der @available-helperen, som ser lidt pænere ud end den længere version fra C, men bruges på nøjagtig samme måde:
if (@available(macOS 10.12, iOS 10.0, *)) {
// Kører på macOS 10.12 eller iOS 10.0 eller højere
}I Swift er der #available, igen er brugen den samme bortset fra det forskellige navn, som vi allerede har dækket. Det er vigtigt at bemærke, at negere kontrollen eller bruge den sammen med andre betingelser ikke understøttes og ikke korrekt beskytter tilgængeligheden. Derudover skal du huske, at dette er en køretidskontrol, så brug af API'er inde i en tilgængelighedskontrol, der mangler i den nuværende SDK-version, der kompileres med, er stadig en fejl.

Kontrol af API-Tilgængelighed ved Kompileringstid (Compile-time) i C/Objective-C
Nogle gange er det nødvendigt at kontrollere tilgængeligheden af en specifik API ved kompileringstid, for eksempel når du vil forblive kompatibel med flere Apple SDK'er, hvor nogle versioner tilbyder nye API'er, som du vil bruge, og nogle versioner mangler denne API.
De vigtigste makroer til dette er:
__<OS-VARIANT>_VERSION_MAX_ALLOWED: Angiver den maksimale version, som vi må bruge API'er fra.__<OS-VARIANT>_VERSION_MIN_REQUIRED: Angiver den minimum påkrævede version, som vi må bruge API'er fra.
<OS-VARIANT> skal erstattes med den OS-variant, vi vil kontrollere, og kan være MAC_OS_X, IPHONE_OS, TV_OS eller WATCH_OS.
Hvis vi f.eks. har en ny API introduceret for macOS 10.12, og vi kompilerer med macOS 10.12 SDK'et, vil __MAC_OS_X_VERSION_MAX_ALLOWED-makroen automatisk blive sat til versionen af SDK'et (f.eks. 101200). Hvis vi ønsker at forblive kompatible med ældre SDK'er, kan vi bruge følgende preprocessor-makroer:
#include <Availability.h>
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200)
if (@available(macOS 10.12, *)) {
// Brug API tilgængelig siden macOS 10.12 SDK
} else {
// Fallback til en anden API tilgængelig i 10.11 og ældre SDK'er
}
#else
// Fallback til en anden API tilgængelig i 10.11 og ældre SDK'er
#endifDet er vigtigt at bemærke, at preprocessor-kontrollerne udføres ved kompileringstid, så korrekt tilgængelighedshåndtering ved køretid er stadig nødvendig. Bemærk, at i Swift er der i øjeblikket ingen direkte måde at kontrollere SDK'en ved kompileringstid på denne måde. Disse makroer er primært relevante for Objective-C/C-kode eller for at forstå de underliggende mekanismer, hvis du arbejder i et blandet projekt.
__<OS-VARIANT>_VERSION_MIN_REQUIRED er nyttig, når du har ældre kode, som du vil deaktivere, når du målretter mod tilstrækkeligt nye Apple OS-versioner. Hvis vi f.eks. har en funktion, der er nødvendig for macOS <= 10.11, kan vi nemt deaktivere den, når vi målretter mod macOS 10.12 eller højere:
#include <Availability.h>
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED <= 101100)
void compat_stuff_for_1011() {
// ... kode kun for macOS 10.11 og ældre
}
#endifDisse avancerede teknikker er mere almindelige i systemnære biblioteker eller i projekter, der skal opretholde en bred kompatibilitet på et lavt niveau.
Sammenfattende Oversigt
| Funktion | Swift | Objective-C / C |
|---|---|---|
| Køretids-tilgængelighedskontrol (kodeblok) | if #available(Platform Version, *) | if (@available(Platform Version, *))if (__builtin_available(Platform Version, *)) |
| Markering af hele metoder/klasser | @available(Platform Version, *) | @available(Platform Version, *) (Objective-C)__attribute__((availability(...))) (C) |
| Kompileringstids-OS-kontrol | #if os(platform)#if targetEnvironment(environment) | #if TARGET_OS_PLATFORM#if __is_target_os(platform)#if __is_target_environment(environment) |
| Kompileringstids-SDK-versionskontrol | Ikke direkte for API-tilgængelighed | __<OS-VARIANT>_VERSION_MAX_ALLOWED__<OS-VARIANT>_VERSION_MIN_REQUIRED |
Ofte Stillede Spørgsmål
- Hvad er "Deployment Target"?
- Dit "Deployment Target" i Xcode er den ældste version af operativsystemet (f.eks. iOS 12.0), som din app er designet til at understøtte. Xcode bruger denne indstilling til at advare dig, hvis du forsøger at bruge API'er, der ikke er tilgængelige på denne version eller ældre.
- Hvorfor er
*nødvendig i#available? - Stjernen (
*) i#available(iOS 13, *)er en wildcard, der betyder "alle andre fremtidige eller ukendte platforme". Den er påkrævet for at sikre, at din kode er fremtidssikret og kan køre på platforme, der måtte blive introduceret efter den version, du specifikt kontrollerer for. - Kan jeg bruge
#availabletil at kontrollere, om en ældre iOS-version kører? #availablekontrollerer, om en specificeret version (eller nyere) er tilgængelig. Du kan ikke direkte bruge den til at kontrollere, om en *ældre* version kører. For at give en fallback-løsning for ældre versioner bruger duelse-blokken i enif #available-sætning.- Hvad sker der, hvis jeg ikke bruger tilgængelighedskontrol?
- Hvis du bruger API'er, der ikke er tilgængelige på en brugers operativsystem, vil din app crashe, når den forsøger at kalde den utilgængelige kode. Dette fører til en dårlig brugeroplevelse og potentielt dårlige anmeldelser i App Store.
- Er
__MAC_OS_X_VERSION_MAX_ALLOWEDrelevant for Swift-udviklere? - Disse preprocessor-makroer er primært relevante for C- og Objective-C-kode. Mens Swift-udviklere sjældent vil bruge dem direkte, er det godt at kende til dem for at forstå, hvordan den underliggende systemkode håndterer tilgængelighed, især i projekter, der blander Swift med C/Objective-C eller interagerer med ældre frameworks.
Konklusion
Swifts tilgængelighedskontrol er en uundværlig funktion for enhver iPhone- og mobilapp-udvikler. Ved at udnytte #available og @available kan du skabe robuste apps, der udnytter de nyeste API'er og funktioner, samtidig med at de forbliver fuldt funktionsdygtige og stabile for brugere på ældre operativsystemer. Dette eliminerer behovet for manuel versionskontrol, reducerer risikoen for nedbrud markant og frigør dig til at fokusere på at bygge fantastiske brugeroplevelser. At mestre disse værktøjer er nøglen til at skrive fremtidssikker og bagudkompatibel kode, hvilket i sidste ende fører til mere succesfulde apps og gladere brugere.
Hvis du vil læse andre artikler, der ligner Swift Tilgængelighedskontrol: Fleksibel App-Udvikling, kan du besøge kategorien Teknologi.
