Foundation Models i SwiftUI: Komplett guide till Apples on-device AI-ramverk

Lär dig bygga intelligenta iOS-appar med Apples Foundation Models. Komplett guide till on-device AI med @Generable för strukturerad output, verktygsanrop och SwiftUI-integration — helt gratis och utan internet.

Introduktion: AI som körs direkt på din iPhone

Under WWDC25 släppte Apple en bomb som fick utvecklarvärlden att ta ett kollektivt andetag: de öppnade upp den on-device språkmodell som driver Apple Intelligence genom ett helt nytt ramverk — Foundation Models. Och nej, det här är inte ännu ett moln-API du måste betala för eller integrera med krångliga OAuth-flöden. Det handlar om en ~3 miljarder parameter stor LLM som körs helt och hållet på användarens enhet. Ingen internetuppkoppling, inga API-nycklar, noll kronor i kostnad.

Låt det sjunka in en sekund.

Med iOS 26 kan du som utvecklare komma åt exakt samma AI-motor som Siri, Skrivverktyg och Photos-analys använder — rakt från din SwiftUI-kod. Det enda du behöver? Tre rader Swift. Och det bästa av allt: all data stannar kvar på enheten. Ingen molntjänst ser någonsin vad dina användare skriver eller frågar om, vilket ärligt talat är en enorm fördel jämfört med de flesta AI-lösningar där ute.

I den här guiden går vi igenom allt du behöver veta för att bygga intelligenta funktioner med Foundation Models-ramverket: grundläggande textgenerering och strömning, avancerad Guided Generation med @Generable, verktygsanrop med Tool-protokollet och prestandaoptimering. Vi tittar även på begränsningar du bör ha koll på och bästa praxis för produktionsappar.

Krav och kompatibilitet

Hårdvarukrav

Foundation Models kräver Apple Silicon med Neural Engine. I klartext innebär det:

  • iPhone: iPhone 15 Pro, iPhone 15 Pro Max och alla iPhone 16-modeller (A17 Pro eller senare)
  • iPad: iPad mini med A17 Pro, samt alla iPad-modeller med M1 eller senare
  • Mac: Alla Mac-datorer med M1-chip eller senare
  • Vision Pro: Apple Vision Pro med M2-chip

Intel-baserade Mac-datorer stöds alltså inte — de saknar den Neural Engine som krävs för effektiv on-device inferens. Om du fortfarande sitter på en Intel-Mac är det kanske dags att uppgradera (sorry, men så är det).

Mjukvarukrav

Du behöver iOS 26, iPadOS 26, macOS 26 (Tahoe) eller visionOS 26 som lägsta deployment target. Användaren måste dessutom ha Apple Intelligence aktiverat i Inställningar, och funktionen måste vara tillgänglig i deras region.

Kontrollera tillgänglighet i kod

Innan du börjar generera text bör din app alltid dubbelkolla att modellen faktiskt är redo. Det här steget är lätt att glömma, men gör det inte — dina användare kommer att tacka dig:

import FoundationModels

let availability = SystemLanguageModel.default.availability

switch availability {
case .available:
    print("Modellen är redo att använda")
case .notAvailable(let reason):
    switch reason {
    case .deviceNotEligible:
        print("Enheten stöder inte Apple Intelligence")
    case .appleIntelligenceNotEnabled:
        print("Apple Intelligence är inte aktiverat")
    case .modelNotReady:
        print("Modellen laddas fortfarande ned")
    @unknown default:
        print("Okänt tillgänglighetsfel")
    }
}

Kom igång: Din första AI-session

Grundläggande textgenerering

Kärnan i Foundation Models-ramverket är egentligen bara två klasser: SystemLanguageModel som ger tillgång till on-device-modellen och LanguageModelSession som hanterar själva konversationen. Så här skapar du din första AI-generering:

import FoundationModels

let session = LanguageModelSession()
let response = try await session.respond(to: "Förklara vad SwiftUI är på ett enkelt sätt")
print(response.content)

Tre rader kod. Det är allt. Ingen konfiguration, inga API-nycklar, ingen nätverkshantering. Jag blev genuint förvånad första gången jag fick det att fungera — det känns nästan för enkelt.

Systeminstruktioner

Du kan ge sessionen instruktioner som styr modellens beteende, ungefär som en systemprompt. En viktig sak att tänka på: instruktionerna bör komma från dig som utvecklare och inte interpoleras med opålitlig användarinput.

let session = LanguageModelSession(instructions: """
    Du är en vänlig Swift-handledare som förklarar koncept 
    med tydliga kodexempel. Svara alltid på svenska.
    Håll svaren koncisa och praktiska.
    """
)

let response = try await session.respond(
    to: "Vad är skillnaden mellan struct och class?"
)
print(response.content)

Sessionen behåller kontext mellan anrop, vilket gör att du kan bygga sammanhängande konversationer där modellen minns vad som sagts tidigare. Rätt smidigt för chatbot-liknande upplevelser.

Förvärmning för bättre prestanda

Vet du att användaren snart kommer använda AI-funktionen? Då kan du förvärma modellen i förväg. Det laddar modellen och fyller promptcachen, vilket gör att första anropet blir drastiskt snabbare:

// Förvärm tidigt, t.ex. i onAppear
try await session.prewarm(promptPrefix: "Du är en matrecept-assistent")

På A18-kisel kan latensen sjunka till under 150 millisekunder med förvärmning. Utan förvärmning tar första anropet märkbart längre tid — och den skillnaden märks i UX:en.

Strömning av svar i realtid

Att tvinga användaren att stirra på en spinner medan hela svaret genereras? Ingen bra upplevelse. Foundation Models stöder strömning som visar texten allt eftersom den genereras — precis som ChatGPT eller Claude gör i sina gränssnitt.

Grundläggande strömning

import SwiftUI
import FoundationModels

struct StreamingView: View {
    @State private var session = LanguageModelSession()
    @State private var outputText = ""
    @State private var isGenerating = false
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            ScrollView {
                Text(outputText)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            
            Button("Generera berättelse") {
                Task {
                    isGenerating = true
                    outputText = ""
                    
                    let stream = session.streamResponse(
                        to: "Skriv en kort saga om en modig utvecklare"
                    )
                    
                    for try await partial in stream {
                        outputText = partial.content
                    }
                    
                    isGenerating = false
                }
            }
            .disabled(isGenerating)
        }
        .padding()
    }
}

En sak att notera här: Foundation Models strömmar ögonblicksbilder (snapshots), inte enskilda tokens. Varje element i den asynkrona sekvensen är det kompletta svaret hittills, inte bara det senaste tillskottet. Det gör SwiftUI-uppdateringarna mycket enklare att hantera — du sätter helt enkelt hela strängen varje gång.

Guided Generation: Strukturerad output med @Generable och @Guide

Okej, nu börjar det bli riktigt spännande. I stället för att be modellen om fri text som du sedan måste parsa (med alla risker för oförutsägbar formatering som det innebär), kan du med Guided Generation tvinga modellen att generera typsäkra Swift-strukturer. Det här är enligt min mening ramverkets absolut mest kraftfulla funktion.

Grunderna i @Generable

Genom att markera en struct med @Generable-makrot berättar du för ramverket att modellen ska producera en instans av just den typen:

import FoundationModels

@Generable
struct Recept {
    let namn: String
    let ingredienser: [String]
    let tillagningstidMinuter: Int
    let instruktioner: [String]
    let svarighet: Svarighet
}

@Generable
enum Svarighet: String {
    case enkel = "Enkel"
    case medel = "Medel"
    case avancerad = "Avancerad"
}

let session = LanguageModelSession()
let response = try await session.respond(
    to: "Ge mig ett recept på pannkakor",
    generating: Recept.self
)

let recept = response.content
print("Recept: \(recept.namn)")
print("Tid: \(recept.tillagningstidMinuter) minuter")
print("Svårighet: \(recept.svarighet.rawValue)")

for ingrediens in recept.ingredienser {
    print("• \(ingrediens)")
}

Det avgörande att förstå är att Guided Generation garanterar strukturell korrekthet genom constrained decoding. Modellen kan helt enkelt inte returnera ogiltig JSON eller sakna fält. Dina prompter kan därmed vara enklare och fokusera på önskat beteende i stället för format — och du slipper den där skakiga JSON-parsningen.

Förfina med @Guide

@Guide-makrot ger dig finare kontroll genom att lägga till begränsningar och beskrivningar på enskilda properties:

@Generable
struct QuizFraga {
    @Guide(description: "En tydligt formulerad fråga om Swift-programmering")
    let fraga: String
    
    @Guide(.count(4))
    let svarsalternativ: [String]
    
    @Guide(description: "Det korrekta svaret, måste vara ett av svarsalternativen")
    let korrektSvar: String
    
    @Guide(description: "En pedagogisk förklaring av varför svaret är korrekt")
    let forklaring: String
}

@Guide stöder flera typer av begränsningar:

  • .count(n) — exakt antal element i en array
  • .range(min...max) — numeriskt intervall
  • .anyOf([alternativ]) — fördefinierade val
  • Regex-mönster — för strängformat som e-post, datum med mera
  • description: — naturligt språk som vägleder modellen

Egenskapsordningen spelar roll

Det här är en detalj som är lätt att missa men riktigt viktig: modellen genererar egenskaper i den ordning de deklareras i din struct. Om en egenskap beror på en annan — till exempel att forklaring refererar till korrektSvar — måste den deklareras efter den egenskap den beror på. Annars kan modellen inte referera till rätt kontext, och du får konstiga resultat.

Komposition och nästlade typer

@Generable stöder full komposition. Du kan nästla strukturer och använda enums för att begränsa möjliga värden, vilket öppnar upp för ganska komplexa datamodeller:

@Generable
struct Resplan {
    let destination: String
    let antalDagar: Int
    let aktiviteter: [Aktivitet]
}

@Generable
struct Aktivitet {
    let namn: String
    let typ: AktivitetsTyp
    let uppskattadTid: String
    let beskrivning: String
}

@Generable
enum AktivitetsTyp: String {
    case sevardheter = "Sevärdheter"
    case matOchDryck = "Mat och dryck"
    case shopping = "Shopping"
    case natur = "Natur"
    case kultur = "Kultur"
}

Strömma strukturerad data

Du kan även strömma delvis genererade strukturer — och det här är riktigt coolt. @Generable-makrot skapar automatiskt en PartiallyGenerated-typ där alla properties är optionella:

let stream = session.streamResponse(
    to: "Skapa en resplan för Stockholm",
    generating: Resplan.self
)

for try await partial in stream {
    if let destination = partial.destination {
        print("Destination: \(destination)")
    }
    if let aktiviteter = partial.aktiviteter {
        print("Aktiviteter hittills: \(aktiviteter.count)")
    }
}

Perfekt för att bygga responsiva gränssnitt som visar data allt eftersom den fylls i. Tänk dig en vy där destinationsnamnet dyker upp direkt, följt av aktiviteter som poppar in en efter en. Användaren ser att något händer, och det ger en mycket bättre upplevelse.

Verktygsanrop: Ge modellen superkrafter

Den on-device-modellen har ingen tillgång till internet och ingen kunskap om realtidsdata. Men — och det här är stort — med verktygsanrop (tool calling) kan du utöka modellens förmågor genom att definiera funktioner som den kan anropa på egen hand under en konversation.

Skapa ett verktyg

Ett verktyg skapas genom att konformera till Tool-protokollet:

import FoundationModels

struct Vaderverktyg: Tool {
    let name = "getCurrentWeather"
    let description = "Hämtar aktuell väderdata för en angiven stad"
    
    @Generable
    struct Arguments {
        @Guide(description: "Stadens namn, t.ex. Stockholm eller Göteborg")
        let stad: String
    }
    
    func call(arguments: Arguments) async throws -> String {
        // Här ansluter du till ditt väder-API
        // I det här exemplet returnerar vi mockdata
        return "Det är 8°C och molnigt i \(arguments.stad)"
    }
}

Lägg märke till att verktygets argument definieras som en @Generable-struct. Det garanterar att modellen aldrig kan producera ogiltiga argument, vilket sparar dig mycket felhantering.

Använda verktyg i en session

let session = LanguageModelSession(
    instructions: "Du är en väderapp-assistent. Använd väderverktyget för att ge aktuell väderinformation.",
    tools: [Vaderverktyg()]
)

let response = try await session.respond(
    to: "Hur är vädret i Malmö just nu?"
)

print(response.content)
// Modellen anropar Vaderverktyg automatiskt, 
// får tillbaka data och formulerar ett naturligt svar

Ramverket hanterar hela flödet automatiskt: modellen analyserar frågan, avgör att den behöver väderdata, genererar korrekta argument, anropar verktyget, tar emot resultatet och formulerar ett svar. Du behöver inte hantera någon av den logiken manuellt. Ganska imponerande, faktiskt.

Flera verktyg och parallella anrop

Du kan registrera flera verktyg i samma session. Ramverket klarar av komplexa grafer av verktygsanrop — både parallella och seriella — och integrerar resultaten i det slutliga svaret:

let session = LanguageModelSession(
    instructions: "Du planerar resor och kan kolla väder och hitta sevärdheter.",
    tools: [Vaderverktyg(), SevardhetsVerktyg(), RestaurangVerktyg()]
)

// Modellen kan anropa flera verktyg i sekvens eller parallellt
let response = try await session.respond(
    to: "Jag ska till Göteborg i helgen. Vad ska jag göra och hur blir vädret?"
)

Integrera med SwiftUI: Ett komplett exempel

Nu sätter vi ihop allt i en komplett SwiftUI-vy. Det här är en AI-driven receptgenerator som visar hur textgenerering, @Generable och felhantering fungerar ihop i praktiken:

import SwiftUI
import FoundationModels

@Generable
struct MatRecept {
    let namn: String
    
    @Guide(.range(1...180))
    let tillagningstidMinuter: Int
    
    let ingredienser: [String]
    let steg: [String]
    
    @Guide(description: "Ett kort tips för att göra rätten extra god")
    let bonusTips: String
}

struct ReceptGeneratorView: View {
    @State private var session = LanguageModelSession(
        instructions: "Du är en erfaren svensk kock. Ge recept med svenska mått och ingredienser."
    )
    @State private var prompt = ""
    @State private var recept: MatRecept?
    @State private var isLoading = false
    @State private var errorMessage: String?
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading, spacing: 20) {
                    promptSection
                    
                    if isLoading {
                        ProgressView("Skapar ditt recept...")
                            .frame(maxWidth: .infinity)
                    }
                    
                    if let recept {
                        receptView(recept)
                    }
                    
                    if let errorMessage {
                        Text(errorMessage)
                            .foregroundStyle(.red)
                    }
                }
                .padding()
            }
            .navigationTitle("AI Receptgenerator")
        }
    }
    
    private var promptSection: some View {
        HStack {
            TextField("Vad vill du laga?", text: $prompt)
                .textFieldStyle(.roundedBorder)
            
            Button("Skapa") {
                Task { await generateRecept() }
            }
            .buttonStyle(.borderedProminent)
            .disabled(prompt.isEmpty || isLoading)
        }
    }
    
    private func receptView(_ recept: MatRecept) -> some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(recept.namn)
                .font(.title2.bold())
            
            Label("\(recept.tillagningstidMinuter) min", 
                  systemImage: "clock")
                .foregroundStyle(.secondary)
            
            Text("Ingredienser")
                .font(.headline)
            ForEach(recept.ingredienser, id: \.self) { ingrediens in
                Text("• \(ingrediens)")
            }
            
            Text("Instruktioner")
                .font(.headline)
            ForEach(Array(recept.steg.enumerated()), id: \.offset) { index, steg in
                Text("\(index + 1). \(steg)")
            }
            
            Label(recept.bonusTips, systemImage: "lightbulb")
                .padding()
                .background(.yellow.opacity(0.1))
                .clipShape(RoundedRectangle(cornerRadius: 8))
        }
    }
    
    private func generateRecept() async {
        isLoading = true
        errorMessage = nil
        recept = nil
        
        do {
            let response = try await session.respond(
                to: prompt,
                generating: MatRecept.self
            )
            recept = response.content
        } catch {
            errorMessage = "Kunde inte generera recept: \(error.localizedDescription)"
        }
        
        isLoading = false
    }
}

Prestanda och optimering

Siffror du bör känna till

Apples on-device-modell levererar överraskande bra prestanda för sin storlek:

  • Time-to-first-token: ~0,6 ms per prompttoken på iPhone 15 Pro
  • Genereringshastighet: ~30 tokens per sekund
  • Med förvärmning: Under 150 ms total latens på A18-kisel

Det är inte i närheten av vad en stor molnmodell klarar i ren kapacitet, men för korta, fokuserade uppgifter på enheten? Mer än tillräckligt.

Optimeringsstrateger

Följ de här principerna för att få ut bästa möjliga prestanda:

  1. Förvärm tidigt: Anropa prewarm() i onAppear eller innan användaren interagerar med AI-funktionen. Den här enskilda åtgärden ger störst skillnad.
  2. Håll @Generable-strukturer minimala: Modellen genererar alla egenskaper, oavsett om de visas. Behöver du bara ett namn och en beskrivning? Skapa inte en struct med tio fält.
  3. Använd strömning: Visa delresultat direkt i stället för att vänta på hela svaret. Användare upplever det som snabbare även om den totala tiden är densamma.
  4. Utnyttja animationer: Kreativa SwiftUI-animationer och övergångar kan maskera genereringslatens effektivt. En snygg fade-in gör underverk.
  5. Testa i Xcode Playgrounds: Apple rekommenderar Playgrounds som det bästa sättet att iterera på prompter. I ett #Playground-block kan du komma åt typer definierade i din app.

Begränsningar att vara medveten om

Foundation Models-ramverket är imponerande, men det finns viktiga begränsningar du måste ha koll på innan du sätter igång:

  • Enbart text: Till skillnad från multimodala modeller som GPT-4o eller Claude accepterar ramverket bara textinput — inga bilder, ljud eller PDF:er. Det begränsar en hel del användningsfall.
  • 4 096 tokens totalt: Både input och output delar på ett fönster om 4 096 tokens (ungefär 3 000 ord). Det gör ramverket olämpligt för långa konversationer eller sammanfattning av stora dokument.
  • Inte för allt: Apple avråder uttryckligen från att använda ramverket för kodgenerering eller matematiska beräkningar — modellen är optimerad för språkförståelse och textgenerering.
  • Innehållsskyddsräcken: Inbyggda guardrails flaggar känsligt innehåll som självskada, våld och sexuellt material. Det kan ibland blockera helt legitima användningsfall, så var beredd på det.
  • Språkstöd: Nio språk stöds: engelska, franska, tyska, italienska, portugisiska, spanska, kinesiska, japanska och koreanska. Svenska saknas tyvärr i listan, men engelska prompter med svenska instruktioner fungerar i praktiken.
  • Regionsbaserad tillgänglighet: Apple Intelligence är inte tillgängligt i alla regioner, vilket kräver robust tillgänglighetskontroll i produktionskod.

Bästa praxis för produktionsappar

Här är de viktigaste sakerna att tänka på när du bygger riktiga appar med Foundation Models:

  1. Kontrollera alltid tillgänglighet: Använd SystemLanguageModel.default.availability innan varje AI-interaktion och ge tydlig feedback om modellen inte är tillgänglig. Det här är inte valfritt.
  2. Separera instruktioner från användarinput: Interpolera aldrig opålitlig användarinput i systeminstruktionerna — det öppnar för prompt injection och det vill du verkligen inte ha.
  3. Hantera fel graciöst: Fånga guardrail-överträdelser, kontextfönsterfel och otillgänglighet med tydliga felmeddelanden. Ingen gillar en app som bara kraschar.
  4. Erbjud fallback: Ha alltid en icke-AI-väg i din app för enheter som inte stöder Foundation Models. Alla dina användare sitter inte på senaste hårdvaran.
  5. Ordna @Generable-egenskaper strategiskt: Deklarera beroende egenskaper i rätt ordning och sammanfattningar sist.
  6. Profilera med Instruments: Använd Xcodes profileringsverktyg för att mäta inferenstid och optimera prompter.

Vanliga frågor

Kostar det något att använda Foundation Models-ramverket?

Nej, och det är en av de bästa sakerna med det här ramverket. Inga API-kostnader, inga rate limits och inga prenumerationsavgifter. Modellen körs på användarens egen enhet och Apple debiterar ingenting för inferensen. Det enda kravet är en kompatibel enhet med iOS 26 eller senare och Apple Intelligence aktiverat.

Kan jag använda Foundation Models offline?

Ja, absolut. Eftersom modellen körs helt on-device behövs ingen internetuppkoppling. Alla AI-funktioner fungerar i flygplansläge, i tunnelbanan eller var som helst utan mottagning. Modellen laddas ned som en del av operativsystemet, inte som en del av din app.

Vad är skillnaden mellan Foundation Models och att anropa ChatGPT:s eller Claudes API?

Den avgörande skillnaden är var beräkningen sker. Foundation Models körs lokalt — all data stannar privat, det kostar ingenting och fungerar offline. Molnbaserade API:er erbjuder kraftfullare modeller med större kontextfönster och multimodala förmågor, men kräver internet, kostar pengar och skickar data till externa servrar. Sammanfattningsvis: Foundation Models lämpar sig bäst för kortare, enhetsnära uppgifter medan moln-API:er passar bättre för komplexa resonemang och stora datamängder.

Vilka typer stöder @Generable?

@Generable stöder structs och enums. De kan byggas med primitiva typer som String, Int, Double, Float, Decimal och Bool. Arrays av dessa typer stöds också. Generable-typer kan nästlas — en struct kan innehålla andra @Generable-strukturer och enums. Ramverket stöder dessutom rekursiva typer, vilket öppnar för avancerade grejer som generativa UI-komponenter.

Ökar Foundation Models storleken på min app?

Nej, inte alls. Modellen är inbyggd i operativsystemet — inte i din app. Din apps binär påverkas inte av att använda ramverket. Du importerar bara FoundationModels som vilket annat Apple-ramverk som helst, och modellvikterna hanteras av iOS.

Om Författaren Editorial Team

Our team of expert writers and editors.