Proč je Foundation Models zlomový okamžik pro iOS vývojáře
Představte si, že můžete ve své aplikaci spustit jazykový model se 3 miliardami parametrů — bez API klíčů, bez serverových nákladů, bez připojení k internetu. A kompletně zdarma. Přesně to Apple přinesl s iOS 26 a frameworkem Foundation Models. Je to tentýž model, který pohání Apple Intelligence, a teď je dostupný přímo ve vašem Swift kódu.
Tohle je prostě velká věc.
Na rozdíl od cloudových řešení jako ChatGPT nebo Claude, kde data putují na vzdálené servery, Foundation Models běží kompletně na zařízení. Veškerá data zůstávají u uživatele, latence je minimální (mluvíme o 0,6 ms do prvního tokenu na iPhone 15 Pro) a generování jede rychlostí přibližně 30 tokenů za sekundu. Pro nás vývojáře to otevírá úplně novou kategorii funkcí — od generování obsahu přes klasifikaci textu až po inteligentní průvodce aplikací.
V tomto průvodci si projdeme Foundation Models od úplných základů až po pokročilé techniky jako guided generation a tool calling. Ke každé sekci přidáme funkční příklady kódu, které můžete rovnou hodit do svých projektů.
Co přesně je Foundation Models framework
Foundation Models je nativní Swift framework představený na WWDC 2025, který dává přístup k on-device jazykovému modelu Apple Intelligence. Nejde o žádný wrapper nad externím API — model s přibližně 3 miliardami parametrů a velikostí kolem 1,6 GB běží přímo na Neural Engine, GPU a CPU vašeho Apple Silicon čipu.
Framework stojí na třech hlavních pilířích:
- Generování textu — odpovídání na dotazy, sumarizace, generování kreativního obsahu
- Guided generation — strukturovaný, typově bezpečný výstup do nativních Swift typů pomocí maker
@Generablea@Guide - Tool calling — model může autonomně volat funkce vaší aplikace pro přístup k externím datům
A co s tím můžete dělat v praxi? Docela dost:
- Návrhy při vyhledávání v aplikaci
- Generativní dialogy pro herní postavy (to je fakt cool)
- Generování a úprava textového obsahu
- Inteligentní průvodce funkcemi aplikace
- Automatická tvorba kvízů a odpovídání na otázky
- Klasifikace obsahu, sumarizace a sémantické vyhledávání
Požadavky a kontrola dostupnosti
Než začnete kódovat, ujistěte se, že máte správné prostředí:
- Hardware: Mac s Apple Silicon (pro vývoj), zařízení s čipem A17 Pro nebo řady M (pro spuštění)
- Systém: macOS Tahoe 26, iOS 26, iPadOS 26 nebo visionOS 26
- IDE: Xcode 26
- Apple Intelligence: musí být zapnutá v Nastavení zařízení
Protože ne každé zařízení Foundation Models podporuje, je zásadní kontrolovat dostupnost modelu za běhu. Nikdo nechce, aby mu aplikace spadla u uživatele se starším iPhonem. Apple k tomu poskytuje vlastnost availability na SystemLanguageModel:
import FoundationModels
let model = SystemLanguageModel.default
switch model.availability {
case .available:
// Model je připraven k použití
print("Model je dostupný")
case .unavailable(.appleIntelligenceNotEnabled):
print("Zapněte Apple Intelligence v Nastavení")
case .unavailable(.modelNotReady):
print("Model se stahuje nebo připravuje, zkuste to později")
case .unavailable(.deviceNotEligible):
print("Toto zařízení nepodporuje Foundation Models")
default:
print("Model není dostupný")
}
Jeden důležitý detail, který se hodí vědět — SystemLanguageModel nabízí dva režimy:
.default— optimalizovaný pro obecné generování textu, kreativní obsah a odpovídání na otázky.contentTagging— specializovaný adaptér pro extrakci entit, detekci témat a automatické tagování
// Obecný model
let generalModel = SystemLanguageModel.default
// Specializovaný model pro tagování obsahu
let taggingModel = SystemLanguageModel(useCase: .contentTagging)
Základy: LanguageModelSession a generování textu
Vstupním bodem do celého frameworku je třída LanguageModelSession. Ta udržuje konverzační kontext mezi jednotlivými požadavky — funguje v podstatě jako chat, kde si model pamatuje předchozí zprávy.
Jednoduchý textový dotaz
Nejjednodušší způsob, jak model použít, je metoda respond(to:):
import FoundationModels
let session = LanguageModelSession()
do {
let response = try await session.respond(
to: "Vysvětli rozdíl mezi struct a class ve Swiftu"
)
print(response.content)
} catch {
print("Chyba: \(error)")
}
Výsledek najdete ve vlastnosti response.content jako String. Metoda je async throws, takže potřebujete try await a standardní ošetření chyb. Nic překvapivého, pokud jste zvyklí na moderní Swift.
Instrukce pro model (systémový prompt)
Pro konzistentní chování modelu můžete nastavit instrukce při vytváření session. Instrukce fungují jako systémový prompt — definují roli, omezení a styl odpovědí:
let session = LanguageModelSession {
"Jsi expertní Swift tutor. Odpovídáš stručně a vždy uvádíš příklady kódu."
"Pokud si nejsi jistý odpovědí, řekni to přímo."
}
let response = try await session.respond(
to: "Jak fungují property wrappery?"
)
print(response.content)
Instrukce se vyhodnocují před uživatelským promptem a nastavují kontext pro celou session. Můžete v nich dokonce uvést příklady (few-shot learning), což podle mé zkušenosti výrazně zlepšuje konzistenci výstupu.
Prewarm pro okamžitou odezvu
Pokud víte, že budete model potřebovat brzy, můžete ho předem načíst do paměti metodou prewarm(). To eliminuje tu nepříjemnou prodlevu při prvním dotazu:
let session = LanguageModelSession()
// Předem načteme model do paměti
try await session.prewarm(promptPrefix: "Jsi vývojářský asistent")
// Pozdější dotaz bude rychlejší
let response = try await session.respond(to: "Co je nového ve Swift 6.2?")
Streamování odpovědí pro plynulé UI
Čekat na kompletní odpověď modelu může znamenat několik sekund ticha v uživatelském rozhraní. A upřímně, to není skvělý zážitek pro uživatele. Proto Foundation Models nabízí streamování — odpověď se zobrazuje postupně, token po tokenu.
import SwiftUI
import FoundationModels
struct ChatView: View {
@State private var session = LanguageModelSession()
@State private var responseText = ""
@State private var isGenerating = false
var body: some View {
VStack {
ScrollView {
Text(responseText)
.padding()
}
Button("Generuj příběh") {
Task {
await generateStory()
}
}
.disabled(isGenerating)
}
}
func generateStory() async {
isGenerating = true
responseText = ""
do {
let stream = session.streamResponse(
to: "Napiš krátký příběh o Swift vývojáři"
)
for try await partial in stream {
responseText = partial
}
} catch {
responseText = "Chyba: \(error.localizedDescription)"
}
isGenerating = false
}
}
Metoda streamResponse(to:) vrací AsyncSequence, kde každý element obsahuje dosud vygenerovaný text. Tady je jedna fajn věc — na rozdíl od některých jiných implementací Foundation Models nestreamuje jednotlivé tokeny, ale kompletní snapshoty odpovědi. Každý nový element obsahuje vše od začátku, takže aktualizace UI je triviální.
Šikovná vlastnost session je také isResponding — hodí se pro blokování nových vstupů během generování:
Button("Odeslat") {
Task { await sendMessage() }
}
.disabled(session.isResponding)
Guided generation: Typově bezpečný výstup s @Generable
Tak, a tady se Foundation Models skutečně odlišuje od konkurence. Guided generation umožňuje modelu generovat přímo nativní Swift struktury místo prostého textu. Žádné parsování JSON řetězců, žádné křehké regulární výrazy — model spolehlivě naplní vámi definovaný typ.
Osobně považuju tuhle funkci za nejsilnější stránku celého frameworku.
Makro @Generable
Označte svůj typ makrem @Generable a Foundation Models automaticky vygeneruje JSON schéma, které model použije pro strukturovaný výstup:
import FoundationModels
@Generable
struct FilmRecenze {
let nazev: String
let hodnoceni: Int
let plusy: [String]
let minusy: [String]
let shrnuti: String
}
let session = LanguageModelSession()
let response = try await session.respond(
to: "Napiš recenzi na film Interstellar",
generating: FilmRecenze.self
)
// response.content je typu FilmRecenze — plně typově bezpečné
print(response.content.nazev) // "Interstellar"
print(response.content.hodnoceni) // 9
print(response.content.plusy) // ["Vizuální efekty", ...]
Všechny vlastnosti v @Generable typu musí být samy generable — tedy primitivní typy (String, Int, Double, Bool), pole, vnořené @Generable typy nebo enumy.
Makro @Guide pro přesnější kontrolu
Když potřebujete modelu přesněji říct, co má generovat, přichází na řadu makro @Guide. Umožňuje přidat popisy vlastností a nastavit omezení na generované hodnoty:
@Generable
struct Recept {
@Guide(description: "Název receptu v češtině")
let nazev: String
@Guide(description: "Doba přípravy v minutách", .range(5...180))
let dobaPripravy: Int
@Guide(description: "Obtížnost receptu", .anyOf(["snadný", "střední", "pokročilý"]))
let obtiznost: String
@Guide(.count(4...8))
let ingredience: [String]
let postup: [String]
}
let session = LanguageModelSession()
let response = try await session.respond(
to: "Navrhni recept na české svíčkové",
generating: Recept.self
)
let recept = response.content
print("\(recept.nazev) — \(recept.obtiznost), \(recept.dobaPripravy) min")
print("Ingredience: \(recept.ingredience.joined(separator: ", "))")
Tady je přehled dostupných omezení v @Guide:
description:— textový popis vlastnosti pro model (upřímně, tohle je to nejdůležitější).range()— omezení číselného rozsahu.anyOf([])— výběr z konkrétních hodnot.count()— přesný počet nebo rozsah prvků pole.constant()— fixní hodnota- Regex vzor — omezení formátu řetězce pomocí regulárního výrazu
Enumy v guided generation
Výčtové typy (enum) jsou v guided generation obzvláště silné. Proč? Protože model může generovat pouze platné hodnoty — žádné překvapení za běhu:
@Generable
enum Nalada: String {
case pozitivni = "pozitivní"
case negativni = "negativní"
case neutralni = "neutrální"
case smisena = "smíšená"
}
@Generable
struct AnalyzaTextu {
let nalada: Nalada
let klicoveBody: [String]
@Guide(description: "Stručné shrnutí v jedné větě")
let shrnuti: String
}
let response = try await session.respond(
to: "Analyzuj tento text: 'Nový iPhone je skvělý, ale cena je příliš vysoká.'",
generating: AnalyzaTextu.self
)
switch response.content.nalada {
case .smisena:
print("Text vyjadřuje smíšené pocity")
default:
print("Nálada: \(response.content.nalada.rawValue)")
}
Streamování strukturovaného výstupu
Guided generation lze kombinovat se streamováním, což je skvělé pro UX. Foundation Models pro to automaticky generuje typ PartiallyGenerated — v podstatě zrcadlovou kopii vašeho @Generable typu, kde jsou všechny vlastnosti optional. Jak model postupně generuje data, vlastnosti se plní jedna po druhé:
@State private var partialRecept: Recept.PartiallyGenerated?
func streamRecept() async {
let stream = session.streamResponse(
to: "Navrhni jednoduchý recept",
generating: Recept.self
)
do {
for try await partial in stream {
partialRecept = partial
}
} catch {
print("Chyba: \(error)")
}
}
// V UI:
if let nazev = partialRecept?.nazev {
Text(nazev).font(.title)
}
if let ingredience = partialRecept?.ingredience {
ForEach(ingredience, id: \.self) { item in
Text("• \(item)")
}
}
Ještě jeden tip na závěr této sekce: vlastnosti se generují v pořadí, v jakém jsou deklarovány ve zdrojovém kódu. Takže pokud je hodnota jedné vlastnosti závislá na jiné (třeba shrnutí závisí na obsahu), umístěte závislou vlastnost až za ty, na kterých závisí.
Tool calling: Rozšiřte schopnosti modelu
On-device model nemá přístup k internetu, aktuálním datům ani k datům vaší aplikace. To dává smysl — běží lokálně a je omezený na to, co zná z tréninku. Přesně pro tyto situace existuje tool calling — mechanismus, kterým model autonomně volá funkce vaší aplikace, když potřebuje další informace.
Definice nástroje
Nástroj vytvoříte implementací protokolu Tool:
import FoundationModels
struct PocasiTool: Tool {
let name = "pocasi"
let description = "Získá aktuální počasí pro zadané město."
@Generable
struct Arguments {
@Guide(description: "Název města, pro které se má zjistit počasí")
let mesto: String
}
func call(arguments: Arguments) async throws -> ToolOutput {
// V reálné aplikaci by zde byl API call
let teplota = 22
let popis = "Polojasno"
return ToolOutput(
"Počasí v \(arguments.mesto): \(teplota)°C, \(popis)"
)
}
}
Protokol Tool vyžaduje čtyři věci:
name— identifikátor nástroje (bez mezer a speciálních znaků)description— popis v přirozeném jazyce, který model použije k rozhodování, kdy nástroj zavolatArguments—@Generablestruktura definující vstupní parametrycall(arguments:)— asynchronní metoda s logikou nástroje, vracejícíToolOutput
Registrace a použití nástrojů
Nástroje se registrují při vytváření session:
let pocasiTool = PocasiTool()
let session = LanguageModelSession(tools: [pocasiTool]) {
"Jsi přátelský asistent pro cestování. Když se uživatel ptá na počasí, použij nástroj pocasi."
}
let response = try await session.respond(
to: "Jaké je počasí v Praze?"
)
print(response.content)
// Model automaticky zavolá PocasiTool a zahrne výsledek do odpovědi
Celý průběh pod kapotou funguje takto:
- Model obdrží dotaz uživatele a vyhodnotí, jestli potřebuje nástroj
- Pokud ano, autonomně vygeneruje argumenty pro nástroj (díky
@Generable) - Framework zavolá metodu
call(arguments:)vašeho nástroje - Výsledek se automaticky vloží zpět do konverzace
- Model vygeneruje finální odpověď s využitím dat z nástroje
A samozřejmě můžete registrovat více nástrojů najednou — model si sám vybere ten správný podle kontextu:
let session = LanguageModelSession(
tools: [pocasiTool, restauraceTool, mapaTool]
) {
"Jsi cestovní průvodce. Pomáhej uživatelům plánovat výlety."
}
Integrace se SwiftUI: Kompletní příklad
Pojďme spojit vše dohromady v reálném příkladu. Postavíme jednoduchý generátor kvízových otázek — je to skvělý způsob, jak si osahat guided generation v praxi:
import SwiftUI
import FoundationModels
@Generable
struct KvizOtazka {
@Guide(description: "Otázka z oblasti programování ve Swiftu")
let otazka: String
@Guide(.count(4))
let moznosti: [String]
@Guide(description: "Správná odpověď — musí být jedna z možností")
let spravnaOdpoved: String
@Guide(description: "Vysvětlení, proč je odpověď správná")
let vysvetleni: String
}
struct KvizView: View {
@State private var session = LanguageModelSession {
"Jsi expert na Swift programování. Generuješ kvízové otázky v češtině."
}
@State private var otazka: KvizOtazka?
@State private var vybranaOdpoved: String?
@State private var zobrazitVysledek = false
@State private var nacitani = false
var body: some View {
NavigationStack {
VStack(spacing: 20) {
if nacitani {
ProgressView("Generuji otázku...")
} else if let otazka {
Text(otazka.otazka)
.font(.headline)
.padding()
ForEach(otazka.moznosti, id: \.self) { moznost in
Button {
vybranaOdpoved = moznost
zobrazitVysledek = true
} label: {
Text(moznost)
.frame(maxWidth: .infinity)
.padding()
.background(barvaOdpovedi(moznost))
.foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
.disabled(zobrazitVysledek)
}
if zobrazitVysledek {
Text(otazka.vysvetleni)
.font(.callout)
.padding()
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
Button(otazka == nil ? "Začít kvíz" : "Další otázka") {
Task { await generovatOtazku() }
}
.buttonStyle(.borderedProminent)
.disabled(nacitani)
}
.padding()
.navigationTitle("Swift kvíz")
}
}
func barvaOdpovedi(_ moznost: String) -> Color {
guard zobrazitVysledek else { return .blue }
if moznost == otazka?.spravnaOdpoved { return .green }
if moznost == vybranaOdpoved { return .red }
return .gray
}
func generovatOtazku() async {
nacitani = true
zobrazitVysledek = false
vybranaOdpoved = nil
do {
let response = try await session.respond(
to: "Vygeneruj novou kvízovou otázku o Swiftu",
generating: KvizOtazka.self
)
otazka = response.content
} catch {
print("Chyba generování: \(error)")
}
nacitani = false
}
}
Tento příklad hezky demonstruje sílu guided generation — celá logika kvízu stojí na tom, že model vrátí přesně definovanou strukturu KvizOtazka s otázkou, čtyřmi možnostmi, správnou odpovědí i vysvětlením. Žádné parsování, žádné hádání formátu. Prostě to funguje.
Kontextové okno a jeho správa
Tady přichází studená sprcha. On-device model má kontextové okno o velikosti 4 096 tokenů. To je pevný limit zahrnující úplně vše — instrukce, historii konverzace, prompt uživatele i odpověď modelu.
Oproti cloudovým modelům s kontextem stovek tisíc tokenů je to výrazné omezení. Ale dá se s tím pracovat.
Praktické rady pro efektivní správu kontextu:
- Udržujte instrukce stručné — každé slovo v systémovém promptu ukrajuje z dostupného prostoru
- Minimalizujte vlastnosti v @Generable typech — model generuje všechny vlastnosti bez ohledu na to, jestli je v UI zobrazíte
- Vytvářejte nové session pro nezávislé dotazy — nepotřebujete udržovat historii, když otázky spolu nesouvisí
- Monitorujte transcript — vlastnost
session.transcriptukazuje kompletní historii konverzace včetně tool callů
Pokud limit překročíte, framework vyhodí chybu. Tu byste měli ošetřit a nabídnout uživateli možnost začít novou konverzaci.
Osvědčené postupy a tipy pro výkon
Po delší práci s Foundation Models se vykrystalizovalo několik pravidel, která fakt dělají rozdíl v kvalitě výsledků:
- Pište instrukce v angličtině — i když generujete obsah v jiném jazyce, anglické systémové prompty produkují konzistentnější výsledky. Model je primárně trénovaný na anglických datech a prostě to poznat
- Volejte
prewarm()co nejdříve — ideálně přionAppearview, kde budete model potřebovat - Používejte streamování — výrazně snižuje vnímanou latenci a uživatelé to ocení
- Udržujte @Generable typy ploché — hluboce vnořené struktury zvyšují latenci a spotřebu tokenů
- Řaďte vlastnosti správně — generují se v pořadí deklarace, shrnutí a závislé hodnoty patří na konec
- Chraňte se před prompt injection — nikdy nevkládejte neošetřený uživatelský vstup přímo do instrukcí. Oddělujte systémový kontext od uživatelských dat
- Testujte na reálných zařízeních — výkon v simulátoru neodpovídá realitě, protože model běží na Neural Engine
Omezení, o kterých byste měli vědět
Foundation Models není všelék. A to je v pořádku — je dobré znát limity předem, než začnete plánovat funkce, které nepůjdou realizovat:
- Pouze text — v současné verzi framework podporuje jen textové úlohy, žádné zpracování obrázků, audia nebo videa
- Není to chatbot pro obecné znalosti — model s 3B parametry exceluje v specifických úlohách (sumarizace, klasifikace, generování krátkého obsahu), ale nemá znalostní bázi srovnatelnou s velkými cloudovými modely
- 4 096 tokenů — pevný limit kontextového okna omezuje délku konverzací a složitost výstupů
- Závislost na hardwaru — vyžaduje Apple Silicon s podporou Apple Intelligence (A17 Pro a novější)
- Pouze Apple platformy — žádná podpora pro Android, web nebo jiné platformy
Často kladené otázky
Musím platit za používání Foundation Models?
Ne. Foundation Models běží kompletně na zařízení uživatele a je zdarma. Nepotřebujete žádné API klíče, předplatné ani serverovou infrastrukturu. Model je jednoduše součástí operačního systému.
Lze on-device model dotrénovat na vlastních datech?
Ne, on-device Foundation Model nelze fine-tunovat. Chování modelu ale můžete ovlivnit prostřednictvím instrukcí (systémového promptu), few-shot příkladů a tool callingu pro přístup k vlastním datům. Pokud potřebujete specializované modely, podívejte se na Create ML nebo Core ML.
Mohu spustit více session paralelně?
Ano, to jde. Jedna session sice nemůže zpracovávat více dotazů najednou (nový respond nelze volat, dokud předchozí nedoběhne), ale můžete vytvořit více nezávislých instancí LanguageModelSession a spouštět je paralelně v různých Task kontextech.
Jaké jazyky model podporuje?
Foundation Models podporuje více jazyků — angličtinu, francouzštinu, němčinu, italštinu, portugalštinu, španělštinu, zjednodušenou čínštinu, japonštinu a korejštinu. Nejlepší výsledky ale dostanete v angličtině. Pro instrukce doporučuji vždy angličtinu, i když generujete obsah v jiném jazyce.
Jak velký je model a ovlivní velikost mé aplikace?
Model má přibližně 1,6 GB, ale je součástí operačního systému — nestahuje se s vaší aplikací. Velikost vaší aplikace tedy nijak neovlivní. Stačí importovat FoundationModels a model je dostupný.