通过更新一个现有的示例 App,我们将向你介绍如何通过 Swift 并发机制来优化 App 的用户体验。我们将从一个主 Actor App 入手,然后根据需要逐步引入异步代码。我们将使用任务来优化主 Actor 上运行的代码,并探索如何通过将工作转移到后台来实现代码并行运行。我们将探讨数据争用安全机制提供的功能,并讲解如何解读和修复数据争用安全错误。最后,我们将展示如何在 App 情境中充分利用结构化并发机制。
首先,我们异步从照片库加载所选照片。以下是 loadPhoto 函数的实现:
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)
}
在 SwiftUI View 中,我们使用 .task 修饰符在视图出现时调用这个异步函数:
StickerPlaceholder()
.task {
await viewModel.loadPhoto(selectedPhoto)
}
接下来,我们同步从照片中提取贴纸和颜色。更新 loadPhoto 函数以使用 PhotoProcessor:
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)
}
存储处理后的照片在一个字典中:
var processedPhotos = [SelectedPhoto.ID: ProcessedPhoto]()
在轮播视图中显示带渐变背景的贴纸,代码如下:
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)
}
}
}
为了允许照片处理在后台线程运行,我们定义 PhotoProcessor 为 nonisolated 并使用 @concurrent:
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? {
// ...
}
}
然后,在 loadPhoto 函数中异步调用 process 方法:
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)
}
为了并行执行贴纸和颜色提取,我们使用 async let:
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? {
// ...
}
}
在轮播中,我们对每个贴纸应用视觉效果:
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)
}
}
}
当从并发任务访问引用类型时,使用 @concurrent:
Task { @concurrent in
await viewModel.loadPhoto(selectedPhoto)
}
使用任务组一次处理所有照片:
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
}
}
}
}
在贴纸网格视图中,启动照片处理并配置分享链接:
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()
}
}
}