Free Frontier AI on iOS 27
![]()
If you're making an app in 2026, you probably have some sort of AI integration. This can make your app feel magical, but it brings a couple of risks, like out-of-control spending, or your API key leaking and someone else using it for their vibe coding.
The Foundation Models framework fixed that last year, but the model had fairly limited abilities. This time around we've had a huge jump, with models that rival the ones you'd normally reach for from OpenAI or Google.
And the best bit? It's free. Like, completely free*
*with limits
PrivateCloudComputeLanguageModelis available in iOS 27, iPadOS 27, macOS 27, watchOS 27, and visionOS 27.
Getting started
Getting started with Foundation Models is trivial. First, import the framework and create a LanguageModelSession.
import FoundationModels
let session = LanguageModelSession(
tools: [],
instructions: """
You're a barista who helps people decide what coffee to drink.
"""
)import FoundationModels
let session = LanguageModelSession(
tools: [],
instructions: """
You're a barista who helps people decide what coffee to drink.
"""
)The instructions stay with the session, so every answer it gives us should come from our helpful little barista rather than a completely generic assistant.
I've passed an empty tools array for now. It doesn't do anything yet, but we'll come back and give it something useful later.
Then, simply ask it to do something.
let response = try await session.respond(
to: """
What's the best coffee if I want something tasty, but not too strong?
"""
)
print(response.content)
// If you're looking for something not too strong but still tasty,
// I'd recommend a light roast. It offers a smooth flavour with
// subtle notes that won't overwhelm your palate.let response = try await session.respond(
to: """
What's the best coffee if I want something tasty, but not too strong?
"""
)
print(response.content)
// If you're looking for something not too strong but still tasty,
// I'd recommend a light roast. It offers a smooth flavour with
// subtle notes that won't overwhelm your palate.That's it. No API key, no network request of our own, and no bill quietly ticking upwards in the background.
This uses the on-device system model by default. It's private, works offline, and has no request limit. For plenty of small features, this is still exactly the model I'd use. You can use it to generate unique copy to welcome a user, search your app's own content, build summarizers for large pieces of text, and more.
If this is your first time using the framework, I wrote a longer introduction to Foundation Models last year. For now, our tiny barista is enough to get us moving.
Getting better intelligence
The on-device model is much better in iOS 27, but it still has to fit on a phone. Some jobs need a bit more room to think.
This year, Apple is giving us access to the much larger model that runs on Private Cloud Compute. Switching to it takes one extra line.
let session = LanguageModelSession(
model: PrivateCloudComputeLanguageModel(),
tools: [],
instructions: """
You're a barista who helps people decide what coffee to drink.
"""
)let session = LanguageModelSession(
model: PrivateCloudComputeLanguageModel(),
tools: [],
instructions: """
You're a barista who helps people decide what coffee to drink.
"""
)And that's the whole change.
The rest of the Foundation Models API stays the same. Our prompts, generated types, and tools don't need a separate cloud implementation. We've just swapped the model underneath them.
It looks like a small change, but it's a very different model. Private Cloud Compute gives us a 32,768-token context window, which leaves space for much larger prompts, longer conversations, and more results from tools. It can also reason before answering, which is where it starts to feel much closer to the big models we've become used to elsewhere.
There are still no API keys to ship, no authentication flow to build, and no token bill for the developer. Apple handles the request through the user's device and iCloud account, and says the data is used only for that request rather than stored.
That is a fairly ridiculous amount of capability for one new initializer.
Enabling Private Cloud Compute
There is one step outside of Xcode. Private Cloud Compute uses a managed entitlement, so the account holder needs to apply for access on the Apple Developer website.
At launch, this is available to developers in the App Store Small Business Program with fewer than two million first-time downloads across their apps. Once Apple assigns the entitlement to your account, it can be included in the provisioning profile for your app.
You can still write and build the code without it, but a distributed app won't be able to use the cloud model until that entitlement is in place. If you try to use, or even instantiate, the model without the entitlement, your app will crash.
Giving it time to think
The cloud model supports three reasoning levels: light, moderate, and deep.
We choose one when asking the session to respond.
let response = try await session.respond(
to: """
I like sweet coffee, but I don't want a drink with loads of milk.
Compare three drinks, work through the trade-offs, then recommend one.
""",
contextOptions: ContextOptions(reasoningLevel: .moderate)
)let response = try await session.respond(
to: """
I like sweet coffee, but I don't want a drink with loads of milk.
Compare three drinks, work through the trade-offs, then recommend one.
""",
contextOptions: ContextOptions(reasoningLevel: .moderate)
)light is useful when the model just needs to gather a little more context. moderate gives it more room to work through the problem, and deep is there for the jobs where the quality of the answer matters more than getting it back immediately.
Deep reasoning can take a while, so I wouldn't turn it on for every button with a sparkle on it. It also uses more of the context window. Start light, test it with real prompts, and only move up when the results are actually better.
For recommending a flat white, deep reasoning is probably a touch dramatic.
Giving it something real to use
Our barista can now give better answers, but it still doesn't know what our coffee shop actually sells. Left to itself, it might confidently recommend something that isn't on the menu.
This is where tool calling comes in. A tool lets the model ask our app for information, or get our app to do a piece of work on its behalf.
We'll start with a small coffee model.
struct Coffee {
let name: String
let summary: String
var modelDescription: String {
"""
Name: \(name)
Summary: \(summary)
"""
}
}
extension Coffee {
static let menu = [
Coffee(
name: "Flat White",
summary: "Smooth and fairly strong, with a small amount of steamed milk."
),
Coffee(
name: "Cappuccino",
summary: "Foamy, balanced, and a little lighter than a flat white."
),
Coffee(
name: "Mocha",
summary: "Sweet chocolate with espresso and steamed milk."
)
]
}struct Coffee {
let name: String
let summary: String
var modelDescription: String {
"""
Name: \(name)
Summary: \(summary)
"""
}
}
extension Coffee {
static let menu = [
Coffee(
name: "Flat White",
summary: "Smooth and fairly strong, with a small amount of steamed milk."
),
Coffee(
name: "Cappuccino",
summary: "Foamy, balanced, and a little lighter than a flat white."
),
Coffee(
name: "Mocha",
summary: "Sweet chocolate with espresso and steamed milk."
)
]
}Next, wrap that menu in a tool.
struct CoffeeMenuTool: Tool {
let name = "coffeeMenu"
let description = "Looks up the coffees available in our shop."
@Generable
struct Arguments {
@Guide(description: "What the person wants from their coffee")
let request: String
}
func call(arguments: Arguments) async throws -> String {
Coffee.menu
.map(\.modelDescription)
.joined(separator: "\n\n")
}
}struct CoffeeMenuTool: Tool {
let name = "coffeeMenu"
let description = "Looks up the coffees available in our shop."
@Generable
struct Arguments {
@Guide(description: "What the person wants from their coffee")
let request: String
}
func call(arguments: Arguments) async throws -> String {
Coffee.menu
.map(\.modelDescription)
.joined(separator: "\n\n")
}
}The Arguments type tells the model what it needs to give our tool. In this case we only want the original request, but the same type could contain dates, IDs, filters, or anything else the tool needs to do its job.
When call runs, we return the menu as text. Tools can return anything that conforms to PromptRepresentable, and a String is all we need here. This could just as easily come from SwiftData, HealthKit, or an API of our own. The model doesn't need to know where it came from, it only needs a useful result.
Now give the tool to our cloud session.
let coffeeTool = CoffeeMenuTool()
let session = LanguageModelSession(
model: PrivateCloudComputeLanguageModel(),
tools: [coffeeTool],
instructions: """
You're a barista who helps people decide what coffee to drink.
Always use the coffeeMenu tool before making a recommendation.
Only recommend drinks returned by that tool.
"""
)let coffeeTool = CoffeeMenuTool()
let session = LanguageModelSession(
model: PrivateCloudComputeLanguageModel(),
tools: [coffeeTool],
instructions: """
You're a barista who helps people decide what coffee to drink.
Always use the coffeeMenu tool before making a recommendation.
Only recommend drinks returned by that tool.
"""
)The session can decide when to call the tool, read its result, and use that result to form the final answer. All of that happens inside the same respond call we started with.
let response = try await session.respond(
to: """
I want something sweet today, but it still needs to taste like coffee.
""",
contextOptions: ContextOptions(reasoningLevel: .moderate)
)
print(response.content)let response = try await session.respond(
to: """
I want something sweet today, but it still needs to taste like coffee.
""",
contextOptions: ContextOptions(reasoningLevel: .moderate)
)
print(response.content)This is where the larger model really starts to earn its keep. Tool calling often means reading a bigger result, deciding whether it has enough information, and sometimes calling more than one tool before answering. The larger context and better reasoning give it much more room to do that well.
I went much deeper into the shape of Tool, streaming its result, and debugging calls in Tool calling with Apple Intelligence. The nice part is that the same tools now work with either model. You don't need to write them again for the cloud.
Returning something we can show
Text is useful for a quick demo, but most apps will want something a little more dependable than a paragraph to pull apart.
@Generable lets us describe the exact result we want.
@Generable
struct CoffeeRecommendation {
@Guide(description: "The exact name of a coffee from the menu")
let name: String
@Guide(description: "A short explanation of why it suits the request")
let reason: String
}@Generable
struct CoffeeRecommendation {
@Guide(description: "The exact name of a coffee from the menu")
let name: String
@Guide(description: "A short explanation of why it suits the request")
let reason: String
}Then ask the same session to generate that type.
let response = try await session.respond(
to: """
I want something sweet today, but it still needs to taste like coffee.
""",
generating: CoffeeRecommendation.self,
contextOptions: ContextOptions(reasoningLevel: .moderate)
)
print(response.content.name)
// Mocha
print(response.content.reason)
// The chocolate makes it sweet, whilst the espresso keeps a clear coffee flavour.let response = try await session.respond(
to: """
I want something sweet today, but it still needs to taste like coffee.
""",
generating: CoffeeRecommendation.self,
contextOptions: ContextOptions(reasoningLevel: .moderate)
)
print(response.content.name)
// Mocha
print(response.content.reason)
// The chocolate makes it sweet, whilst the espresso keeps a clear coffee flavour.We now have the full path: a session, a better model, some data from our app, and a result SwiftUI can show without any string parsing.
Free, but not infinite
Here's the asterisk from the start.
Private Cloud Compute doesn't charge us per token, but each user has a daily usage limit tied to their iCloud account. An iCloud+ subscription can give them a higher limit, but we still need to handle the moment they reach it.
The model exposes that state directly.
let model = PrivateCloudComputeLanguageModel()
if model.quotaUsage.isLimitReached {
// Disable the cloud-powered action and explain why.
}let model = PrivateCloudComputeLanguageModel()
if model.quotaUsage.isLimitReached {
// Disable the cloud-powered action and explain why.
}We can also tell when someone is getting close.
if case .belowLimit(let info) = model.quotaUsage.status,
info.isApproachingLimit {
// Let the person know before they spend their last few requests.
}if case .belowLimit(let info) = model.quotaUsage.status,
info.isApproachingLimit {
// Let the person know before they spend their last few requests.
}Apple can provide an action for managing or increasing the limit. Rather than hiding it in a dismissible alert, put it near the feature that has stopped working.
if let suggestion = model.quotaUsage.limitIncreaseSuggestion {
Button("Show options") {
suggestion.show()
}
}if let suggestion = model.quotaUsage.limitIncreaseSuggestion {
Button("Show options") {
suggestion.show()
}
}Private Cloud Compute also needs an internet connection and a device that supports Apple Intelligence. Check availability before showing the feature, and keep the on-device model around as a fallback where the task is small enough.
private let cloudModel = PrivateCloudComputeLanguageModel()
var body: some View {
if cloudModel.isAvailable {
CoffeeRecommendationView()
} else {
OnDeviceCoffeeRecommendationView()
}
}private let cloudModel = PrivateCloudComputeLanguageModel()
var body: some View {
if cloudModel.isAvailable {
CoffeeRecommendationView()
} else {
OnDeviceCoffeeRecommendationView()
}
}The useful distinction is that the on-device model is free and unlimited, whilst Private Cloud Compute is free and much more capable. The cost has moved away from us, but capacity hasn't stopped existing.
Still, being able to add a private, reasoning cloud model to an app without building a backend, protecting a secret key, or wondering what launch day will do to the bill is a huge deal.
Wrapping up
Foundation Models started as a lovely way to add small, private bits of intelligence to an app. In iOS 27, that same API can reach a much larger model, reason through harder requests, and work through far more context without us taking on the usual server mess.
Start with LanguageModelSession. Add PrivateCloudComputeLanguageModel when the job needs more intelligence. Then bring in generated types and tools as the feature earns them.
That feels like a much nicer way to build AI features: begin with three lines, and only add complexity when the app has something useful to do with it.
You can learn more in Apple's What's new in the Foundation Models framework and Private Cloud Compute sessions.
If you have questions, or something cool to share, I'm @SwiftyAlex on twitter.