Skip to main content
3Nsofts logo3Nsofts
On-Device AI

Apple Intelligence Integration in iOS Apps: A Production Guide

A practical guide to shipping Foundation Models features: availability, session lifecycle, snapshot streaming, guided generation, fallbacks, testing, and privacy boundaries.

By Ehsan Azish · 3NSOFTS·May 2026·12 min read

Adding generative AI to an iOS app is no longer synonymous with adding a cloud dependency. Apple's Foundation Models framework exposes the on-device language model that powers Apple Intelligence through a native Swift API.

That changes the privacy and offline story, but it does not remove the hard production work. A shippable feature still needs accurate availability handling, bounded tasks, cancellation, predictable output, fallbacks, and tests that survive model updates.

This guide focuses on those decisions. It avoids fixed performance promises because latency varies by device, prompt, output length, thermal state, and OS model version. Measure the packaged app on the devices you support.


Choose the Right Apple AI API

Two APIs are commonly confused:

  • Foundation Models provides access to Apple's system language model. It is suited to tasks such as summarization, extraction, classification, content generation, and tool-assisted workflows.
  • Core ML runs a model that you package or download. It is the better fit when you need a particular vision, audio, embedding, or domain-specific model.

Foundation Models is not a replacement for every Core ML model or server-scale model. Apple specifically advises designing focused tasks rather than relying on the on-device model for broad world knowledge or advanced reasoning. Start with a narrow user outcome and evaluate it with representative inputs.

For a direct comparison, see Foundation Models vs Core ML.

Availability Is Runtime State

Do not infer support from a model name, chip family, or OS version alone. Device eligibility, Apple Intelligence settings, region, language, and model readiness can all affect availability.

Use the framework's availability API:

import FoundationModels

enum IntelligenceState {
    case ready
    case unavailable(SystemLanguageModel.Availability.UnavailableReason)
}

func intelligenceState() -> IntelligenceState {
    switch SystemLanguageModel.default.availability {
    case .available:
        return .ready
    case .unavailable(let reason):
        return .unavailable(reason)
    }
}

Check this when presenting or invoking the feature. A model can be temporarily not ready, so a value captured only at launch can become stale. In the UI, distinguish a temporary setup state from an ineligible device when possible, and always keep the non-AI workflow usable.

Keep Sessions Aligned With User Tasks

LanguageModelSession maintains context between requests. Reuse a session when turns belong to the same conversation or task, and start a new one when the user begins a different task or when old context would contaminate the result.

import FoundationModels

@MainActor
final class SummaryModel: ObservableObject {
    @Published private(set) var summary = ""
    @Published private(set) var isWorking = false

    private let session = LanguageModelSession(instructions: """
        Summarize only the supplied text.
        Preserve concrete facts and do not invent missing details.
        """)

    func summarize(_ text: String) async {
        guard SystemLanguageModel.default.isAvailable else { return }

        isWorking = true
        defer { isWorking = false }

        do {
            let response = try await session.respond(to: text)
            summary = response.content
        } catch is CancellationError {
            // Cancellation is expected when the user leaves or starts over.
        } catch {
            // Present a recoverable state and keep the original content intact.
        }
    }
}

Create and mutate the session from a clearly owned concurrency domain, such as a main-actor view model or a dedicated actor. The important production rule is to prevent overlapping requests from competing for the same user-visible state. Cancel obsolete work when the input or screen changes.

If predictable first-use responsiveness matters, investigate the session's prewarm API and verify its memory and launch impact on real hardware rather than publishing a universal timing claim.

Streaming Produces Snapshots, Not Token Deltas

Foundation Models streaming is an async sequence of progressively complete snapshots. For plain text, render the current snapshot instead of appending it as though every element were a new token:

func streamSummary(_ text: String) async throws {
    guard SystemLanguageModel.default.isAvailable else { return }

    for try await snapshot in session.streamResponse(to: text) {
        summary = snapshot.content
    }
}

This distinction matters. Appending snapshots duplicates text, while replacing the displayed value tracks the response correctly. Keep view identity stable and avoid expensive layout work on every update.

Use a non-streaming response when the output is short or must be complete before the app can validate or apply it.

Use Guided Generation for Structured Results

Do not ask the model to imitate JSON and then repair malformed output. Guided generation uses Swift types marked with @Generable; @Guide can describe a property or constrain allowed values.

import FoundationModels

@Generable
struct ArticleLabels {
    @Guide(description: "A concise topic label")
    var topic: String

    @Guide(.anyOf(["low", "medium", "high"]))
    var urgency: String

    @Guide(description: "No more than three short keywords", .maximumCount(3))
    var keywords: [String]
}

let response = try await session.respond(
    to: "Classify this support message: \(message)",
    generating: ArticleLabels.self
)

let labels = response.content

GenerationOptions controls generation behavior such as sampling. It is not the schema definition mechanism. The schema comes from the generable type or a dynamic generation schema.

Structured output guarantees shape, not truth. Validate business rules after generation and require user confirmation before destructive, financial, medical, or externally visible actions.

Tools Expand Capability—and the Trust Boundary

Tool calling lets the model request code that your app defines. A tool may search local data, query an API, or prepare an action, but the model should not receive unrestricted access to the app.

For each tool:

  • expose the narrowest useful input and output
  • validate every argument in normal Swift code
  • enforce authentication and authorization outside the model
  • require confirmation for consequential actions
  • make cancellation and timeout behavior explicit
  • disclose network use when a tool leaves the device

The system language model can run offline, but a network-backed tool cannot. Product copy and privacy disclosures must describe the complete feature, not only the model.

Core ML Still Owns Custom Inference

Use Core ML when you need a model chosen for a specific task: image classification, audio analysis, embeddings, anomaly detection, or a domain model with an evaluation set your team controls.

Model size, latency, memory, battery use, and accuracy trade off against each other. Compression can help, but there is no defensible universal claim that a quantized model has negligible accuracy loss or always completes within a fixed number of milliseconds. Benchmark the exact converted model on the oldest supported device and record:

  • cold and warm latency distributions
  • peak memory and app termination behavior
  • energy impact during repeated inference
  • accuracy before and after compression
  • behavior under Low Power Mode and thermal pressure

See the Core ML implementation guide for that path.

Writing Tools Require Product Judgment

Standard text-editing surfaces can participate in system Writing Tools. That is useful for prose, but may be inappropriate for code, structured syntax, or fields where automatic rewriting changes meaning.

Where supported by your deployment target, configure the behavior deliberately:

TextEditor(text: $content)
    .writingToolsBehavior(.disabled)

Test the actual interaction on every supported platform. System availability and presentation can differ by device, language, and OS configuration.

Privacy Claims Must Match the Whole Data Flow

Apple describes Foundation Models' system language model as on-device: prompts and responses for that model stay on the device, and the model can work offline. That is a strong property, but it is easy to overstate.

A feature is not fully on-device if your app also sends prompts, tool arguments, analytics payloads, or fallback requests to a server. Document each boundary separately:

  1. what the on-device model receives
  2. what tools can access
  3. whether any tool or fallback uses the network
  4. what is logged or retained
  5. what happens when the model is unavailable

If you offer a cloud fallback, make it visible and consensual. Do not silently turn an on-device feature into a network feature.

A Production Checklist

Before shipping:

  • Gate the feature using SystemLanguageModel.default.availability.
  • Keep a useful non-AI path and recheck temporary unavailable states.
  • Give each session a clear lifetime and cancel obsolete requests.
  • Prefer guided generation for structured data, then validate its meaning.
  • Test prompts across supported languages and current OS model versions.
  • Test the packaged app on the oldest and newest supported hardware.
  • Measure latency, memory, energy, and cancellation instead of publishing guessed numbers.
  • Constrain tools and require confirmation for consequential actions.
  • Keep privacy copy aligned with tools, analytics, and fallbacks.
  • Re-run evaluations when Apple updates the system model.

The strongest Foundation Models features are usually not chat screens. They are focused capabilities that improve an existing workflow, fail safely, and disappear gracefully when the model is unavailable.

References

Related Reading

Authoritative References