---
title: "Ship On-Device AI with Apple Foundation Models + SwiftUI"
published: true
description: "Wire Apple's on-device Foundation Models to SwiftUI: streaming with AsyncSequence, memory constraints, and latency tradeoffs for iOS 18.4+."
tags: [swift, ios, mobile, architecture]
canonical_url: https://mvpfactory.co/blog/apple-foundation-models-swiftui
---
What You Will Build
By the end of this tutorial you will have a SwiftUI view that streams private, on-device AI inference using Apple's Foundation Models framework — no server, no API key, no data leaving the device. You will also have a production-ready hardware-gated fallback path for older devices, because shipping means handling the whole device matrix.
Prerequisites: Xcode 16+, iOS 18.4+ simulator or device (A17 Pro or M-series chip), Swift concurrency fundamentals.
Step 1 — Understand the Session API
Let me show you a pattern I use in every project. Foundation Models exposes inference through LanguageModelSession. The surface area is deliberately small — do not let that fool you into thinking it is limited.
import FoundationModels
let session = LanguageModelSession(
instructions: "You are a concise summarization assistant."
)
let response = try await session.respond(
to: "Summarize this note in one sentence.",
options: .init(temperature: 0.7)
)
print(response.content)
Inference never leaves the device. For health, finance, or productivity apps handling sensitive personal data — think break-reminder apps like HealthyDesk that sit on top of personal usage patterns — this eliminates an entire privacy risk class and removes server inference costs entirely.
Step 2 — Stream Responses into SwiftUI
Synchronous respond(to:) works for short completions. For anything the user reads in real time, use streaming. The docs do not always make this obvious, but AsyncSequence + the task modifier is the idiomatic path here — not Combine.
@State private var output = ""
var body: some View {
ScrollView { Text(output).padding() }
.task {
let session = LanguageModelSession()
for try await partial in session.streamResponse(to: prompt) {
output += partial.text
}
}
}
No third-party dependencies. No manual dispatch queue management. Most teams reach for Combine out of habit — resist that. AsyncSequence + task is already there and composes exactly right for this pattern.
Step 3 — Gate on Hardware and Build the Fallback Path
Here is the minimal setup to get this working across your real device matrix.
Apple's model sits at approximately 3 billion parameters (per WWDC 2025 "Explore the Foundation Models framework"). Memory headroom varies significantly:
| Chip Tier | Neural Engine | Unified Memory | Context Headroom |
|---|---|---|---|
| A17 Pro | 16-core | 8 GB | ~4 GB usable for ML |
| A18 / A18 Pro | 16-core (2nd gen) | 8 GB | Higher throughput |
| M1 / M2 (iPad, Mac) | 16–32 core | 8–16 GB | Lowest fallback risk |
| A16 and below | Not supported | — | Always route to server |
Build the fallback as a first-class path — not an afterthought:
func runInference(prompt: String) async throws -> String {
guard LanguageModelSession.isSupported else {
return try await serverInference(prompt: prompt)
}
let session = LanguageModelSession(
instructions: "You are a concise assistant."
)
let response = try await session.respond(to: prompt)
return response.content
}
func serverInference(prompt: String) async throws -> String {
var request = URLRequest(url: serverEndpointURL)
request.httpMethod = "POST"
request.httpBody = try JSONEncoder().encode(["prompt": prompt])
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(InferenceResponse.self, from: data).text
}
Note: Verify the exact symbol name —
LanguageModelSession.isSupportedor equivalent — against the current SDK docs before shipping. This API surface was evolving through the iOS 18.x cycle.
Gotchas
Prompt Guard runs on every call and you cannot disable it. It screens for prompt injection and policy violations before the model processes input. Apple has not published per-device latency figures for this layer — treat any specific numbers in circulation with skepticism until you measure on your target hardware. The practical implication: do not build flows that depend on sub-100ms first-token latency on the initial session call.
Skipping representative prompt testing is how you discover your fallback path in production. Build a small evaluation suite of realistic prompts early. Run it on A17 Pro, M-series, and an unsupported device before App Store review. Evaluation is part of shipping.
Do not reach for CoreML just because it feels more engineered. Foundation Models wins on time-to-production for general language tasks. CoreML wins when you need domain-specific accuracy or deterministic sub-50ms inference. The integration and maintenance cost of a custom CoreML pipeline is real and ongoing — choose it deliberately.
Conclusion
Three things to internalize before you ship:
- Gate on hardware availability from day one and ship the fallback path alongside it — device capability is a first-class architectural input.
- Use
AsyncSequence+taskfor all streaming UI — it is idiomatic Swift concurrency and requires zero extra dependencies. - Run your prompt suite across the full device tier matrix before you hit App Store review.
The session API is minimal by design. The privacy guarantee is real. The memory constraints are real too — knowing where that line sits for your prompts is the engineering work that separates a shipped app from a demo.
Resources:
Top comments (0)