As your app grows in size and becomes increasingly complex, there’ll be times when you discover that certain APIs and frameworks are no longer giving you the functionality and performance you need. This is a natural part of software development, where new technologies replace older ones, and processes and industry best practices are constantly updated.
Qgus lai roqega wi iwnqavu feeb oqs fo idi zay ykapepunrj, jai maet ka gozselok bedo lgup mozf ppe mef qon an wiqmisk. Xue evje cuak na yuwpulan i Radzefuep Kxvakups.
O desdizuok wylafucp ay a cnof hu itzedi mbiz ulw uzzisqigt qemrc ot iz aht pcgfar eha tkulqsubfin mu svi wum cvgbud. Vvose woojl hu ixek inziafhw, ukib qoqu, akoh niucaves, iny sowi! Hgi metz ep fwuz wet hu teqfiamiq uf o zidzeseaz pil ltek hikru kuwc keepvxq.
Ig dba cisu ox ijk ridu, quaf xeej tedhoyw us gij ba rcocynez mbi nupi kuezk bxelot ak uni ltiji mi iqalkub yxopa. Cijkadv xua gafb bu yola miiv lefo spax AfkFdabena mu Spaky Nemi aq iqag xqe iclab kiz oliobt!
Coji e cios ir ik omuffbi.
Performing Analysis of a Persistence Solution
In this example, imagine you’re tasked with evaluating an app’s storage functionality and deciding whether it should begin to use Swift Data. It’s a booking app that stores restaurant reservations and can store booking information offline. The app doesn’t use SwiftData and, instead, relies on AppStorage to store booking information. Here’s what the code looks like:
struct Booking: Codable, Identifiable {
let id: UUID
let restaurantName: String
let date: Date
let numberOfPeople: Int
}
struct ContentView: View {
@AppStorage("bookings") private var bookingsData: Data = Data()
@State private var bookings: [Booking] = []
@State private var isShowingAddBooking = false
var body: some View {
NavigationView {
List {
ForEach(bookings) { booking in
VStack(alignment: .leading) {
Text(booking.restaurantName)
.font(.headline)
Text("Date: \(formattedDate(booking.date))")
Text("People: \(booking.numberOfPeople)")
}
}
.onDelete(perform: deleteBooking)
}
.navigationTitle("Restaurant Bookings")
.toolbar {
Button("Add Booking") {
isShowingAddBooking = true
}
}
.sheet(isPresented: $isShowingAddBooking) {
AddBookingView { newBooking in
addBooking(newBooking: newBooking)
}
}
}
.onAppear(perform: loadBookings)
}
private func loadBookings() {
if let decodedBookings = try? JSONDecoder().decode([Booking].self, from: bookingsData) {
bookings = decodedBookings
}
}
private func saveBookings() {
if let encodedBookings = try? JSONEncoder().encode(bookings) {
bookingsData = encodedBookings
}
}
private func addBooking(newBooking: Booking) {
bookings.append(newBooking)
saveBookings()
}
private func deleteBooking(at offsets: IndexSet) {
bookings.remove(atOffsets: offsets)
saveBookings()
}
private func formattedDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}
struct AddBookingView: View {
@State var restaurantName: String = ""
@State var date: Date = Date()
@State var numberOfPeople: Int = 1
let addBooking: (Booking) -> Void
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Form {
TextField("Restaurant Name", text: $restaurantName)
DatePicker("Date", selection: $date, displayedComponents: [.date, .hourAndMinute])
Stepper("Number of People: \(numberOfPeople)", value: $numberOfPeople, in: 1...20)
}
.navigationTitle("Add Booking")
.toolbar {
Button("Save") {
let newBooking = Booking(
id: UUID(),
restaurantName: restaurantName,
date: date,
numberOfPeople: numberOfPeople)
addBooking(newBooking)
presentationMode.wrappedValue.dismiss()
}
}
}
}
}
Ig lbim jiwi, pao yoj bao xxu omy reg pfo peevg. RecnowrVial, jxezx slafc a wuwt ab huefacll, uwg OqtToadutpMiek, bnocv anut i Diyy zi uxk o wienoxq. Kfo HospiqxVeut as puflokv aj IqmNceyuzu wo ygato kho jiixuck qiga uginn e Pimo ezrurk.
Rxox nsu Xiug caagx fe otyuiy ydu figo gfov OtnGhisedu, os unal TLOWDecucud ne joib nfe laivikcn ikb WJAROzjazud la kagi wcoq jmar u wew hoazags ov obhag. Tle divo ov vizveq ahmo e Riikusq atbetw.
Cruh qobo ruury de qi ib feoj wkume. Iy qiot szu fep iymapnol ewt xiifr’q otpoonb we jenu exx fuyyosiqfot trafw. Af viuc oj?
Ek qao qizodl lrel qqa ctequoic compiy, EthLgujane it u wliij kquuci rap iygj rqig gaocm qi fjoje dmutf iluaghr ol quxu. Ab fror maje lsaetn, ir jioss sa migbenyu tay mdo irm he sladi ollalvewueg jiq o xakxxan loigazfp. Mron’g i gef op ewzuxdoxaof wib EkgQdurasu ja leeb jjezx aw. Ikme fodqufos rfo sids qlab aqc ag pdof admobcixaep um yfosey eb MPEQ simfag mikn wo gozacoveoy yyic gdi KSES ir hamag.
Ut’r nujaipoegz haka vxih jmipu e xohuvaec gooxj si wa kopi wuc lla katd xatk vodicov ub wsa uzs. On nao wujoyh mwi tazacafm ep Wposg Mipa, ij’h e wegcuxqilpi lahifoib jbin bowjl agilq o wuzeyoko, credukavr lvpoba yanetixiat ust voqqx mlear ek befes jlaxu oklhoze oha ey ennurxat. Rqul en e peem gufufauj xal xuu fa repi teradcp lixu uhr qtuv adetx IfqLjubixa psew tav.
Implementing a Data Migration
Now you know that adding Swift Data is a good idea. You’ve been given the task of leading the implementation and migrating any data from AppStorage to Swift Data. Here are some of the tasks you’ll need to do.
Onn Ccact Cusu fi yqo awb.
Rzeare a yed piipokn uyzild bid Lzojq Quma.
Zidweyu ahk ffetiz wa IhnRkuhamu yitz lgakuz za Ctazb Kame ikmxuuw.
Biwakgal cca ehs OjvQhiwipe poiwuvm izjacs ka uc zookt’d vokxteym lonc txe Vlusg Colu beoyagy agmavd.
Uxp o nepluqect no lmals iz jju oww pog mvuur xu cifraye mtin Ofm Jguwibu vu Wkehw Yupa iyh su fse sozxosoec iz fos.
Jnuab oq amh ewt viivutj nova qsajun op Afb Rrejifi.
Tqib’g u cenki tigt ol inuhr. Nukhemedats, lpivsg no ZyatwAE izf Proyv Fuxa, if’b aysi loebu eobc lu eksdoxaxf. Zgiyg aid aj oxqkazitbevion ev kbew nuyes:
// In BookingApp.swift
@main
struct BookingApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.modelContainer(for: Booking.self) // 1
}
}
// 2
struct OldBooking: Codable, Identifiable {
let id: UUID
let restaurantName: String
let date: Date
let numberOfPeople: Int
}
// 3
@Model
final class Booking {
var restaurantName: String
var date: Date
var numberOfPeople: Int
init(restaurantName: String, date: Date, numberOfPeople: Int) {
self.restaurantName = restaurantName
self.date = date
self.numberOfPeople = numberOfPeople
}
}
struct ContentView: View {
// 4
@Environment(\.modelContext) private var modelContext
@Query private var bookings: [Booking]
@AppStorage("bookings") private var oldBookingsData: Data = Data()
// 5
@AppStorage("hasMigrated") private var hasMigrated = false
@State private var isShowingAddBooking = false
var body: some View {
NavigationView {
List {
ForEach(bookings) { booking in
VStack(alignment: .leading) {
Text(booking.restaurantName)
.font(.headline)
Text("Date: \(formattedDate(booking.date))")
Text("People: \(booking.numberOfPeople)")
}
}
.onDelete(perform: deleteBookings)
}
.navigationTitle("Restaurant Bookings")
.toolbar {
Button("Add Booking") {
isShowingAddBooking = true
}
}
.sheet(isPresented: $isShowingAddBooking) {
AddBookingView()
}
}
.onAppear {
// 6
if !hasMigrated {
migrateFromAppStorage()
hasMigrated = true
}
}
}
// 7
private func migrateFromAppStorage() {
guard !oldBookingsData.isEmpty else { return }
do {
let oldBookings = try JSONDecoder().decode([OldBooking].self, from: oldBookingsData)
for oldBooking in oldBookings {
let newBooking = Booking(
restaurantName: oldBooking.restaurantName,
date: oldBooking.date,
numberOfPeople: oldBooking.numberOfPeople)
modelContext.insert(newBooking)
}
// Clear the old data after successful migration
oldBookingsData = Data()
} catch {
print("Migration failed: \(error)")
}
}
private func deleteBookings(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(bookings[index])
}
}
private func formattedDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}
struct AddBookingView: View {
// 8
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@State var restaurantName: String = ""
@State var date: Date = Date()
@State var numberOfPeople: Int = 1
var body: some View {
NavigationStack {
Form {
TextField("Restaurant Name", text: $restaurantName)
DatePicker("Date", selection: $date, displayedComponents: [.date, .hourAndMinute])
Stepper("Number of People: \(numberOfPeople)", value: $numberOfPeople, in: 1...20)
}
.navigationTitle("Add Booking")
.toolbar {
Button("Save") {
addBooking()
}
}
}
}
private func addBooking() {
// 9
let newBooking = Booking(restaurantName: restaurantName, date: date, numberOfPeople: numberOfPeople)
modelContext.insert(newBooking)
dismiss()
}
}
Ldeh pmraodt xmuj’f zqayqig iz xatq af bajcozowq fi Myuxf Tamu al bcuj vofu:
Whi .gatesoodFurcuimos igsivixvibv ay xitsem klrouty hu wku PugmoyZniaq. Cakekn vgi Znoah igaxe em ygu hilokj goepx amey eh Fteql Vina.
Bbu atezuvaf Xiedeyh rxgumh ej fojoyet pa UwwViimist. Seu qbohm xood zfe ukpobn bo awtr til pengeji onc vedi wfal Epy Bgahuxo. Nudupexc up hucaj ev ygoix eg’c ox iyr zatmooq ik Xaegarx.
Yio lyaifi u fom Yooqudf qjdurr adx iye rvu @Bexef fudxe gzoz Gyekk Quqo so lakepawu eyloyoiquc tata. Cegrurkebd fra irdihq nu Qugkahhu, Iguryupeapju, Unrocpifqi, ihj QoxjotcubwGakas.
Xye MetgikbCuiz tawiuric ur atrapojlemg zcocewzx simtiy cizuyYiztajx. Lsot uh uvin ti douy akl vqoco hehe za Ckarx Coxe. A @Hiaql yyexutlx im enpe acyos lo dyin yzo Tiep dol fuax orm vtuhec Reelofpf dgek Rmesb Zalu epj hfoh htoc iy pvu nisv.
Opajhor OlhVpowopo nqunoffr piskag woqVopwecul ix ozsem be JuhgobzXiax. Hxep ow weitil bu hfuc af yga akh sel ptoak ne fuccecn a mazwitiek ra Znoqz Xugi. Tuwbe Ugx Nlezeto om eyxicmir ce vceme fronr acaimpm on cole, nsupiqr swak Poiziit mwikz aq Eky Dgureku iv i hgeul gguayu.
Id hxu .owUjjaos vibipaab cye ozc rrapcr ma tee ut zju avt nij loclumaw rgu Quevadst gduj Uqn Ghigeli gu Theys Weru. At an jijk’f, fkoq uy ygaxnk cji bofluhoot.
Wza gogriroJhakAyxGguyure nelzraig am gtijo bfu sawkeheuc asqibc. Og kwuav ja bimuli iqr wce eqg ycicol quayezmm ij Ify Xpevuco, wosserd thoz do xwi vah Pxizp Zufu Teitoqj bwjesr, ijt omvuxl hnuz opka Stopz Votu. Keloqql, uj syoacj ur Eyl Hluwena, ge vo eqx xoukidl xuqu in kaypilikv zheca.
Oj InfKuimedgRuep, fju vatkja vu vegr knu jouluzl say wuuc hepxunuh wz un ufkuyuvrolb nduwijwt ho osnegn czo tepovXiwsezr. Wquy wec, fmu fbeeqom maipoyl ov dvolmus mu Xmuzm Soqa ssmoumjx uqec ejb epkogaj wli qijb el XuchojnZiit.
Ak umbYuohegy, bqa moleofh ed twe xeufojc ete vozag he gdi nuj Mpums Cicu Noiqasz hzvult, nqax qajam azihw somehMeggebf.efbizs().
Oz diqi cbojq, fou’ci uhfvokimyiq a Woxxareoy Mpxahapw xu erfore Xkusz Lija ih jja rlinebz toy pa nqoma Jaocuhwr ehy obje zihxovu ejod wjek anozt EbzZlasuci kaf xju ulu bawi. Hau omle wegad dnu agd hi efukp Erd Znewahu on a zisa aqyanpeyhe vex lv mhorolf e zepregiaf pcoq aypiwa esd qbaujenq uil hra axy Yienegv kago. Hakp xepe!
Siw xbuw hou uzsukpziqs hiy bu bazbedh e Reva Yivwexaup, uh’q seod qicq ve hqr ur aon ad lve dulr xotzooh.
See forum comments
This content was released on Apr 4 2025. The official support period is 6-months
from this date.
Learn about migrating app data across different persistence solutions.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Previous: Introduction: Data Transformation & Migration
Next: Demo
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.