防止卡顿的方法

YVTU

使用缓存

在 iOS 上使用 NSCache 可以有效减少主线程上的工作负担,特别是在需要频繁访问和重复计算的情况下。以下是一个简单的 SwiftUI 示例,展示如何在应用中使用 NSCache 来缓存图像以减少加载时间和主线程的负担。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import SwiftUI

// 使用 `NSCache` 创建一个单例 `ImageCache` 类,用于缓存图像。
class ImageCache {
static let shared = NSCache<NSString, UIImage>()
}

// 这是一个自定义的视图,用于异步加载和显示图像。
struct AsyncImageView: View {
let url: URL
@State private var image: UIImage? = nil

var body: some View {
Group {
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFit()
} else {
ProgressView() // 显示加载进度
// `onAppear` 修饰符用于在视图出现时启动图像加载。
.onAppear {
loadImage()
}
}
}
}

// 在 `loadImage` 函数中,首先尝试从 `NSCache` 中获取图像。如果图像已经缓存,则直接使用;否则,它将在后台线程上异步加载图像,并在加载完成后更新 UI。
private func loadImage() {
// 尝试从缓存中获取图像
if let cachedImage = ImageCache.shared.object(forKey: url.absoluteString as NSString) {
self.image = cachedImage
return
}

// 异步加载图像
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url), let loadedImage = UIImage(data: data) {
// 将图像存储到缓存中
ImageCache.shared.setObject(loadedImage, forKey: url.absoluteString as NSString)

// 更新 UI 必须在主线程上
DispatchQueue.main.async {
self.image = loadedImage
}
}
}
}
}

// 显示 `AsyncImageView`,并提供要加载的图像 URL。
struct ContentView: View {
let imageUrl = URL(string: "https://www.example.com/image.jpg")! // 替换为实际的图像 URL

var body: some View {
AsyncImageView(url: imageUrl)
.frame(width: 300, height: 300)
.padding()
}
}

@main
struct CacheExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

添加观察者

使用通知中心(NotificationCenter)来监听通知是一种常见的方式,可以有效地减少主线程的负担,从而提高应用的响应性。你可以通过添加观察者来监听特定的通知,并在接收到通知时再执行相应的操作,避免在主线程上一直等待,阻碍其它交互操作。以下是一个简单的 SwiftUI 示例,展示如何使用 NotificationCenter 来监听和处理通知。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import SwiftUI
import Combine

// 自定义通知名称
extension Notification.Name {
static let customNotification = Notification.Name("customNotification")
}

struct ContentView: View {
@State private var value: Int = 0
private var notificationObserver: AnyCancellable?

var body: some View {
VStack {
Text("Value: \(value)")
.padding()

// 当用户点击按钮时,`NotificationCenter.default.post` 方法会发布一个通知,并在 `userInfo` 中传递一个新的值。
Button("Increase Value") {
// 发送通知
NotificationCenter.default.post(name: .customNotification, object: nil, userInfo: ["newValue": value + 1])
}
}
// 在 `onAppear` 中,通过 `NotificationCenter.default.publisher(for:)` 添加一个通知观察者。当接收到通知时,`sink` 闭包会被调用,更新视图中的状态。
.onAppear {
// 监听自定义通知
notificationObserver = NotificationCenter.default.publisher(for: .customNotification)
.sink { notification in
if let newValue = notification.userInfo?["newValue"] as? Int {
value = newValue
}
}
}
// 在 `onDisappear` 中,通过调用 `cancel` 方法取消观察者,避免潜在的内存泄漏。
.onDisappear {
// 取消观察者订阅
notificationObserver?.cancel()
}
}
}

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

异步

为了避免主线程卡死,可以利用 Swift Concurrency 技术(如 async/await)将耗时的计算任务转移到后台线程执行,同时在任务完成后将结果更新到主线程上。下面是一个使用 Swift Concurrency 技术的 SwiftUI 示例,展示如何在后台线程执行预加载任务,并在主线程上更新 UI。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import SwiftUI

struct ContentView: View {
@State private var data: String = "Loading..."

var body: some View {
VStack {
Text(data)
.padding()

// 用户可以点击按钮来手动触发数据加载任务,这时任务会在后台线程执行,而不会阻塞主线程。
Button("Load Data") {
Task {
await loadData()
}
}
}
.onAppear {
// 在 SwiftUI 的 `onAppear` 或者按钮点击事件中,使用 `Task {}` 启动一个异步任务。这可以让你在不阻塞主线程的情况下执行耗时操作。
Task {
// `loadData()` 函数是一个异步函数,在这个函数中使用 `await` 来等待 `performHeavyTask()` 的结果。`performHeavyTask()` 模拟了一个耗时的任务,如预加载数据或复杂的计算。
await loadData()
}
}
}

// 异步加载数据
func loadData() async {
// 在后台线程上执行耗时任务
let result = await performHeavyTask()

// 为了更新主线程上的 UI,你需要显式地将操作切换回主线程。`MainActor.run {}` 确保在主线程上执行更新操作。
await MainActor.run {
data = result
}
}

// 模拟耗时任务
func performHeavyTask() async -> String {
// 模拟一个耗时的操作,比如一个复杂的计算或数据加载任务。实际开发中,这可以替换为网络请求或数据库操作等。
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2秒

return "Data Loaded"
}
}

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

这个示例展示了如何使用 Swift Concurrency 技术将耗时任务移到后台线程执行,从而避免主线程卡顿,并在任务完成后安全地更新 UI。这样可以确保你的应用在执行复杂任务时依然保持流畅的用户体验。

On this page
防止卡顿的方法