2025 Developer ToolsSwift
WWDC25 · 33 min · Developer Tools / Swift
Code-along: Elevate an app with Swift concurrency
Learn how to optimize your app’s user experience with Swift concurrency as we update an existing sample app. We’ll start with a main-actor app, then gradually introduce asynchronous code as we need to. We’ll use tasks to optimize code running on the main actor, and discover how to parallelize code by offloading work to the background. We’ll explore what data-race safety provides, and work through interpreting and fixing data-race safety errors. Finally, we’ll show how you can make the most out of structured concurrency in the context of an app.
Watch at developer.apple.com ↗Chapters
- 0:00 — Introduction
- 2:11 — Approachable concurrency configuration
- 2:51 — Sample app architecture
- 3:42 — Asynchronously loading photos from the photo library
- 9:03 — Extracting the sticker and the colors from the photo
- 12:30 — Running tasks on a background thread
- 15:58 — Parallelizing tasks
- 18:44 — Preventing data races with Swift 6
- 27:56 — Controlling asynchronous code with structured concurrency
- 31:36 — Wrap-up
Code shown on screen · 12 snippets
Asynchronously loading the selected photo from the photo library
func loadPhoto(_ item: SelectedPhoto) async {
var data: Data? = try? await item.loadTransferable(type: Data.self)
if let cachedData = getCachedData(for: item.id) { data = cachedData }
guard let data else { return }
processedPhotos[item.id] = Image(data: data)
cacheData(item.id, data)
} Calling an asynchronous function when the SwiftUI View appears
StickerPlaceholder()
.task {
await viewModel.loadPhoto(selectedPhoto)
} Synchronously extracting the sticker and the colors from a photo
func loadPhoto(_ item: SelectedPhoto) async {
var data: Data? = try? await item.loadTransferable(type: Data.self)
if let cachedData = getCachedData(for: item.id) { data = cachedData }
guard let data else { return }
processedPhotos[item.id] = PhotoProcessor().process(data: data)
cacheData(item.id, data)
} Storing the processed photo in the dictionary
var processedPhotos = [SelectedPhoto.ID: ProcessedPhoto]() Displaying the sticker with a gradient background in the carousel
import SwiftUI
import PhotosUI
struct StickerCarousel: View {
var viewModel: StickerViewModel
private var sheetPresented: Bool = false
var body: some View {
ScrollView(.horizontal) {
LazyHStack(spacing: 16) {
ForEach(viewModel.selection) { selectedPhoto in
VStack {
if let processedPhoto = viewModel.processedPhotos[selectedPhoto.id] {
GradientSticker(processedPhoto: processedPhoto)
} else if viewModel.invalidPhotos.contains(selectedPhoto.id) {
InvalidStickerPlaceholder()
} else {
StickerPlaceholder()
.task {
await viewModel.loadPhoto(selectedPhoto)
}
}
}
.containerRelativeFrame(.horizontal)
}
}
}
.configureCarousel(
viewModel,
sheetPresented: $sheetPresented
)
.sheet(isPresented: $sheetPresented) {
StickerGrid(viewModel: viewModel)
}
}
} Allowing photo processing to run on the background thread
nonisolated struct PhotoProcessor {
let colorExtractor = ColorExtractor()
func process(data: Data) async -> ProcessedPhoto? {
let sticker = extractSticker(from: data)
let colors = extractColors(from: data)
guard let sticker = sticker, let colors = colors else { return nil }
return ProcessedPhoto(sticker: sticker, colorScheme: colors)
}
private func extractColors(from data: Data) -> PhotoColorScheme? {
// ...
}
private func extractSticker(from data: Data) -> Image? {
// ...
}
} Running the photo processing operations off the main thread
func loadPhoto(_ item: SelectedPhoto) async {
var data: Data? = try? await item.loadTransferable(type: Data.self)
if let cachedData = getCachedData(for: item.id) { data = cachedData }
guard let data else { return }
processedPhotos[item.id] = await PhotoProcessor().process(data: data)
cacheData(item.id, data)
} Running sticker and color extraction in parallel.
nonisolated struct PhotoProcessor {
func process(data: Data) async -> ProcessedPhoto? {
async let sticker = extractSticker(from: data)
async let colors = extractColors(from: data)
guard let sticker = await sticker, let colors = await colors else { return nil }
return ProcessedPhoto(sticker: sticker, colorScheme: colors)
}
private func extractColors(from data: Data) -> PhotoColorScheme? {
let colorExtractor = ColorExtractor()
return colorExtractor.extractColors(from: data)
}
private func extractSticker(from data: Data) -> Image? {
// ...
}
} Applying the visual effect on each sticker in the carousel
import SwiftUI
import PhotosUI
struct StickerCarousel: View {
var viewModel: StickerViewModel
private var sheetPresented: Bool = false
var body: some View {
ScrollView(.horizontal) {
LazyHStack(spacing: 16) {
ForEach(viewModel.selection) { selectedPhoto in
VStack {
if let processedPhoto = viewModel.processedPhotos[selectedPhoto.id] {
GradientSticker(processedPhoto: processedPhoto)
} else if viewModel.invalidPhotos.contains(selectedPhoto.id) {
InvalidStickerPlaceholder()
} else {
StickerPlaceholder()
.task {
await viewModel.loadPhoto(selectedPhoto)
}
}
}
.containerRelativeFrame(.horizontal)
.visualEffect { [selection = viewModel.selection] content, proxy in
let frame = proxy.frame(in: .scrollView(axis: .horizontal))
let distance = min(0, frame.minX)
let isLast = selectedPhoto.id == selection.last?.id
return content
.hueRotation(.degrees(frame.origin.x / 10))
.scaleEffect(1 + distance / 700)
.offset(x: isLast ? 0 : -distance / 1.25)
.brightness(-distance / 400)
.blur(radius: isLast ? 0 : -distance / 50)
.opacity(isLast ? 1.0 : min(1.0, 1.0 - (-distance / 400)))
}
}
}
}
.configureCarousel(
viewModel,
sheetPresented: $sheetPresented
)
.sheet(isPresented: $sheetPresented) {
StickerGrid(viewModel: viewModel)
}
}
} Accessing a reference type from a concurrent task
Task { in
await viewModel.loadPhoto(selectedPhoto)
} Processing all photos at once with a task group
func processAllPhotos() async {
await withTaskGroup { group in
for item in selection {
guard processedPhotos[item.id] == nil else { continue }
group.addTask {
let data = await self.getData(for: item)
let photo = await PhotoProcessor().process(data: data)
return photo.map { ProcessedPhotoResult(id: item.id, processedPhoto: $0) }
}
}
for await result in group {
if let result {
processedPhotos[result.id] = result.processedPhoto
}
}
}
} Kicking off photo processing and configuring the share link in a sticker grid view.
import SwiftUI
struct StickerGrid: View {
let viewModel: StickerViewModel
private var finishedLoading: Bool = false
var body: some View {
NavigationStack {
VStack {
if finishedLoading {
GridContent(viewModel: viewModel)
} else {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
}
}
.task {
await viewModel.processAllPhotos()
finishedLoading = true
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if finishedLoading {
ShareLink("Share", items: viewModel.selection.compactMap {
viewModel.processedPhotos[$0.id]?.sticker
}) { sticker in
SharePreview(
"Sticker Preview",
image: sticker,
icon: Image(systemName: "photo")
)
}
}
}
}
.configureStickerGrid()
}
}
} Resources
Related sessions
-
28 min -
43 min -
25 min -
24 min