-
Codificação guiada: aprimore um app usando a concorrência do Swift
Saiba como otimizar a experiência de usuário do seu app com a concorrência do Swift enquanto atualizamos um app de exemplo existente. Começaremos com um app que será o ator principal e, em seguida, apresentaremos gradualmente o código assíncrono, conforme necessário. Usaremos tarefas para otimizar a execução de código no ator principal e descobriremos como paralelizar o código transferindo o trabalho para o segundo plano. Vamos explorar o que a segurança na corrida de dados oferece e interpretar e corrigir erros de segurança na corrida de dados. Por fim, mostraremos como você pode aproveitar ao máximo a concorrência estruturada no contexto de um app.
Capítulos
- 0:00 - Introdução
- 2:11 - Configuração de concorrência acessível
- 2:51 - Arquitetura do app de exemplo
- 3:42 - Carregamento de fotos da fototeca de forma assíncrona
- 9:03 - Extrair o adesivo e as cores da foto
- 12:30 - Executar tarefas em uma thread em segundo plano
- 15:58 - Tarefas em paralelo
- 18:44 - Evitar corridas de dados com o Swift 6
- 27:56 - Controlar código assíncrono com concorrência estruturada
- 31:36 - Conclusão
Recursos
Vídeos relacionados
WWDC25
WWDC23
-
Buscar neste vídeo...
-
-
6:29 - 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) } -
6:59 - Calling an asynchronous function when the SwiftUI View appears
StickerPlaceholder() .task { await viewModel.loadPhoto(selectedPhoto) } -
9:45 - 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) } -
9:56 - Storing the processed photo in the dictionary
var processedPhotos = [SelectedPhoto.ID: ProcessedPhoto]() -
10:45 - Displaying the sticker with a gradient background in the carousel
import SwiftUI import PhotosUI struct StickerCarousel: View { @State var viewModel: StickerViewModel @State 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) } } } -
14:13 - Allowing photo processing to run on the background thread
nonisolated struct PhotoProcessor { let colorExtractor = ColorExtractor() @concurrent 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? { // ... } } -
15:31 - 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) } -
20:55 - Running sticker and color extraction in parallel.
nonisolated struct PhotoProcessor { @concurrent 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? { // ... } } -
24:20 - Applying the visual effect on each sticker in the carousel
import SwiftUI import PhotosUI struct StickerCarousel: View { @State var viewModel: StickerViewModel @State 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) } } } -
26:15 - Accessing a reference type from a concurrent task
Task { @concurrent in await viewModel.loadPhoto(selectedPhoto) } -
29:00 - 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 } } } } -
30:00 - Kicking off photo processing and configuring the share link in a sticker grid view.
import SwiftUI struct StickerGrid: View { let viewModel: StickerViewModel @State 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() } } }
-