Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 1, 2026

LegacyYou are viewing Gen 1 documentation. Switch to the latest Gen 2 docs →

Optional: Local caching

To guarantee quick and continuous access for customers, Amplify DataStore employs a local cached version of remote data. During your transition from Amplify DataStore, you should choose a local storage solution that aligns with your models to avoid unnecessary complexity.

DataStore directly uses SQLite APIs, but the option you choose may depend on your caching requirements.

Apollo normalized cache

Apollo iOS includes the option to use SQLite as a normalized cache. The normalized cache is straightforward to set up and allows Apollo to replay previous queries from the cache to improve latency, reduce bandwidth consumption, and replay queries while offline.

For setup instructions on the normalized cache, see the Migrate DataStore to Apollo — ObserveQuery section which covers the watch() function that leverages the cache.

Custom caching with SwiftData

For more advanced use cases where you want richer access to cached data, you can use your own caching layer instead of, or in addition to, Apollo's provided cache. Two popular frameworks for this approach are SwiftData and CoreData. The following examples use SwiftData.

Local models

You can use the Model() macro to annotate a separate entity for local storage that corresponds to your Apollo generated model:

@Model
class PostEntity {
@Attribute(.unique) var id: String
var updatedAt: String
var createdAt: String
var status: PostEntityStatus?
var title: String
var rating: Int?
var content: String?
init(id: String,
updatedAt: String,
createdAt: String,
status: PostEntityStatus? = nil,
title: String,
rating: Int? = nil,
content: String? = nil) {
self.id = id
self.updatedAt = updatedAt
self.createdAt = createdAt
self.status = status
self.title = title
self.rating = rating
self.content = content
}
}
enum PostEntityStatus: Codable {
case active
case inactive
}

You can then configure the model storage using ModelContainer:

let container = try ModelContainer(for: PostEntity.self)

Model mapping

Extension initializers can help map the remote model to your local model:

extension PostEntity {
convenience init(postDetails: PostDetails) {
self.init(id: postDetails.id,
updatedAt: postDetails.updatedAt ?? "",
createdAt: postDetails.createdAt ?? "",
status: (postDetails.status == .case(.active)) ? .active : .inactive,
title: postDetails.title,
rating: postDetails.rating,
content: postDetails.content)
}
}

Populate the cache

Once your SwiftData models have been set up, you can populate the data by writing the results of Apollo queries into the cache:

func populate(apolloClient: ApolloClient, context: ModelContext) {
var nextToken: String? = nil
repeat {
let query = GetPostsQuery(
nextToken: nextToken.flatMap { .some($0) } ?? .none)
apolloClient.fetch(query: query) { result in
guard let data = try? result.get().data else { return }
if let items = data.listPosts?.items {
for item in items {
guard let postDetails = item?.fragments.postDetails
else { continue }
let model = PostEntity(postDetails: postDetails)
context.insert(model)
try? context.save()
}
}
nextToken = data.listPosts?.nextToken
}
} while (nextToken != nil)
}

Observability

You can observe the .NSPersistentStoreRemoteChange notification to listen to changes in the persistent store. The notification object contains the transaction's history token which you can use to fetch and process the history of transactions.