「OC」GCD的相关练习
文章目录
- 「OC」GCD的相关练习
-
- 前言
- [问题一:使用 GCD 实现并发任务限制](#问题一:使用 GCD 实现并发任务限制)
- 问题2:任务完成后的顺序执行
- [问题三:定时器与 GCD](#问题三:定时器与 GCD)
- [问题四:异步加载多个图片并更新 UI](#问题四:异步加载多个图片并更新 UI)
- 问题五:多个任务的优先级控制
- 问题七:并行执行多个下载任务,并等待所有任务完成后进行操作
- 问题八:队列中任务的延迟执行
- 问题九:在后台线程中同时执行多个任务并在主线程中汇总结果
- [问题十:使用 GCD 实现懒加载](#问题十:使用 GCD 实现懒加载)
- 总结
前言
为了掌握GCD的相关用法,我让GPT给我出了十道与GCD操作相关的题目,来巩固我对GCD的用法
问题一:使用 GCD 实现并发任务限制
问题描述: 你需要在应用中同时进行多个耗时的任务(如网络请求、文件下载等),但为了避免线程过多导致性能问题,你希望限制同时运行的任务数。具体要求如下:
- 最大并发数为 5。
- 当超过 5 个任务时,其他任务应该等待,直到有任务完成后才能继续执行。
要求: 使用 GCD 的 信号量 (dispatch_semaphore
)来限制最大并发数。
我写的代码:
objc
dispatch_semaphore_t sem = dispatch_semaphore_create(5);
for (int i = 0 ; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
dispatch_semaphore_signal(sem);
});
}
[NSThread sleepForTimeInterval:41];
在这里为了防止主线程提前结束,我在主线程之中简单的做了[NSThread sleepForTimeInterval:41];
来防止主线程提前结束,无法进行异步打印
GPT做出的优化:
objc
dispatch_semaphore_t sem = dispatch_semaphore_create(5);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0 ; i < 20; i++) {
dispatch_group_async(group, queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"第 %d 个任务 正在线程 %@ 执行", i, [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
dispatch_semaphore_signal(sem);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"已完成所有任务");
可以看到,GPT使用了一个group来进行完成等待机制,只有当这个组内的任务全部执行完毕,才会结束主线程,这就比我粗暴的休眠主线程更加便利。
问题2:任务完成后的顺序执行
问题描述: 你需要在后台异步执行三个任务,确保任务按顺序完成,并且每个任务的执行都依赖前一个任务的完成结果。例如:
- 第一个任务:下载数据。
- 第二个任务:处理数据。
- 第三个任务:展示数据。
要求:
- 使用 GCD 来确保任务按照顺序执行。
- 不要在主线程中阻塞其他任务。
提示: 可以考虑使用 dispatch_group
来管理多个任务并保证顺序执行。
我写的代码:
objc
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_SERIAL);
dispatch_group_async(group, queue, ^{
NSLog(@"下载数据");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_async(group, queue, ^{
NSLog(@"解析数据");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_async(group, queue, ^{
NSLog(@"展示数据");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务完成");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
我直接使用一个异步串行队列,对相应任务进行操作,这样保证顺序是一定的。
GPT给出的优化:
没什么区别,直接贴出来
objc
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_SERIAL);
dispatch_group_async(group, queue, ^{
NSLog(@"下载数据");
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"下载完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"解析数据");
[NSThread sleepForTimeInterval:1];
NSLog(@"解析完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"展示数据");
[NSThread sleepForTimeInterval:1];
NSLog(@"展示完成");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务完成");
});
// 如果不需要阻塞主线程,这部分代码可以省略
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"所有任务都完成,主线程继续");
问题三:定时器与 GCD
问题描述: 你需要实现一个定时器功能,要求每隔 2 秒执行一次任务,直到任务执行 5 次。执行完后,停止定时器。
要求:
- 使用
dispatch_after
或dispatch_source
来实现定时任务。 - 确保每次任务执行间隔为 2 秒。
- 完成 5 次任务后停止定时器。
我写的代码
使用dispatch_source_t
objc
__block int count = 0;
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"执行任务 %d",count);
count++;
if (count == 5) {
dispatch_source_cancel(timer);
}
});
// 设置定时器取消时的清理任务
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"定时器已取消,清理资源");
});
dispatch_activate(timer);
dispatch_main();
GPT给出的方法:
GPT使用dispatch_after
,用递归进行类似功能的完成
objc
__block int count = 0; // 定义计数器
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_CONCURRENT);
// 定义一个递归任务
void (^recursiveTask)(void) = ^{
if (count < 5) {
NSLog(@"执行任务 %d", count);
count++;
// 递归调用,延迟2秒后执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, recursiveTask);
} else {
NSLog(@"任务完成");
}
};
// 开始任务
recursiveTask();
// 保持主线程运行
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:15]];
使用了一个递归调用,其中[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:15]];
,用于保证主线程继续运行,能够正常处理各种可能到来的事件。
问题四:异步加载多个图片并更新 UI
问题描述: 你需要从网络异步加载 10 张图片,并在加载完成后更新 UI(显示在一个图片画廊中)。要求:
- 使用
dispatch_async
来加载每张图片。 - 确保 UI 更新发生在主线程。
- 加载完成后,显示一个提示"图片加载完成"。
提示: 可以使用 dispatch_group
来等待所有图片加载完成。
objc
NSMutableArray *ImageArr = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
NSString *imageUrlString = [NSString stringWithFormat:@"https://example.com/image-%d.jpg", i];
NSURL *imageUrl = [NSURL URLWithString:imageUrlString];
dispatch_group_enter(group);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:imageUrl.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
UIImage *image = [UIImage imageWithData:responseObject];
if (image) {
[ImageArr addObject:image];
}
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"图片加载失败: %@", error.localizedDescription);
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成");
});
}
这里我使用AFN第三方库去模拟这个下载图片的过程,这些获取的图片都是按照下载完成顺序的。
GPT给出的优化:
在原先的代码,使用 NSMutableArray
在多个线程中进行修改,而 NSMutableArray
本身不是线程安全的。需要确保在访问时进行线程同步。另外这样下载数据并不能保证图片数据会根据图片原先URL的位置去存储,因为下载结束的时间不同。每次循环都创建新的 AFHTTPSessionManager
实例。
objc
NSMutableArray *ImageArr = [NSMutableArray arrayWithCapacity:10]; // 假设有10张图片
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
NSString *imageUrlString = [NSString stringWithFormat:@"https://example.com/image-%d.jpg", i];
NSURL *imageUrl = [NSURL URLWithString:imageUrlString];
dispatch_group_enter(group);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:imageUrl.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
UIImage *image = [UIImage imageWithData:responseObject];
if (image) {
// 保证按索引顺序存储图片
@synchronized (ImageArr) {
[ImageArr replaceObjectAtIndex:i withObject:image];
}
}
dispatch_group_leave(group); // 完成任务
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"图片加载失败: %@", error.localizedDescription);
dispatch_group_leave(group);
}];
}
// 等待所有任务完成后进行操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成,下载的图片数:%lu", (unsigned long)ImageArr.count);
// 这里可以更新UI,例如显示图片
});
为了确保图片下载的顺序与任务的发起顺序一致,为每个下载任务分配一个索引,确保图片按照该索引顺序被存储。使用一个数组,保存图片的下载顺序。每次下载成功后,将图片放入正确的位置。
问题五:多个任务的优先级控制
问题描述: 你需要执行一系列任务,其中:
- 一些任务优先级较高,需要尽快完成。
- 其他任务可以延迟执行。 要求:
- 使用
dispatch_queue_t
配合 优先级 来实现任务的优先级控制。 - 高优先级任务要尽可能先执行,低优先级任务可以等待高优先级任务完成后执行。
我写的代码:
objc
void highPriorityTask(int i) {
NSLog(@"高优先级任务 %d 开始", i);
[NSThread sleepForTimeInterval:1]; // 模拟任务耗时
NSLog(@"高优先级任务 %d 结束", i);
}
void lowPriorityTask(int i) {
NSLog(@"低优先级任务 %d 开始", i);
[NSThread sleepForTimeInterval:1]; // 模拟任务耗时
NSLog(@"低优先级任务 %d 结束", i);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_queue_t highPrioityQueue = dispatch_queue_create("com.text.highProrityQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(highPrioityQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
dispatch_queue_t lowPriorityQueue = dispatch_queue_create("com.text.lowProrityQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(lowPriorityQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
for (int i = 0; i < 3; i++) {
dispatch_async(highPrioityQueue, ^{
highPriorityTask(i);
});
}
// 提交低优先级任务到低优先级队列
for (int i = 0; i < 3; i++) {
dispatch_async(lowPriorityQueue, ^{
lowPriorityTask(i);
});
}
[NSThread sleepForTimeInterval:10];
}
return 0;
}
这里我使用了dispatch_set_target_queue
,让自定义队列的队列目标分别设置为高优先级和低优先级的队列,得到以下结果
可以看到其实对于高优先级,由于 dispatch_set_target_queue
仅将目标队列的优先级调整了,但如果高优先级任务尚未开始,低优先级的任务也可能被调度。也就是说,对于不同优先级的任务,并不会在任务调度的中途进行抢占。换句话说,GCD 不会中断正在运行的低优先级任务来立即执行高优先级任务。
GPT给出的优化:
为了保证在高优先级的任务先完成,而不先启动低优先级的任务,GPT给我加了一个栅栏函数,保证在高优先级的任务先完成,再完成低优先级的任务
objc
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 提交高优先级任务到并发队列
for (int i = 0; i < 3; i++) {
dispatch_async(concurrentQueue, ^{
highPriorityTask(i);
});
}
// 使用 dispatch_barrier_async 确保前面的高优先级任务全部完成
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"=== 所有高优先级任务完成,开始执行低优先级任务 ===");
});
// 提交低优先级任务到并发队列
for (int i = 0; i < 3; i++) {
dispatch_async(concurrentQueue, ^{
lowPriorityTask(i);
});
}
dispatch_main();
6. 问题:延迟任务执行
问题描述: 你需要在一定延迟后执行一个操作,比如用户点击按钮后 3 秒钟才执行某个任务。要求:
- 使用
dispatch_after
来延迟任务的执行。 - 打印"延迟执行的任务已开始"。
提示: 考虑在主线程中触发延迟任务。
我写的代码:
objc
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
NSLog(@"%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任务完成");
这个代码和GPT的给出的大致一致,就不贴出来了
问题七:并行执行多个下载任务,并等待所有任务完成后进行操作
问题描述: 你需要并行下载多个文件,并在所有文件下载完成后更新 UI。具体要求:
- 假设你有一个包含多个文件 URL 的数组。
- 对每个文件 URL 进行异步下载。
- 当所有文件下载完成后,更新 UI,显示"所有文件已下载"。
提示: 可以使用 dispatch_group
来监听所有异步任务的完成。
我写的代码:
objc
NSArray* fileUrls = @[@"https://example.com/file1.txt", @"https://example.com/file2.txt", @"https://example.com/file3.txt"];
NSMutableArray* downloadedFiles = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_CONCURRENT);
for (NSString *str in fileUrls) {
dispatch_group_enter(group);
NSURL *url = [NSURL URLWithString:str];
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
@synchronized (downloadedFiles) {
[downloadedFiles addObject:data];
dispatch_group_leave(group);
}
});
}
dispatch_group_notify(group, queue, ^{
NSLog(@"所有任务完成");
});
在以上代码,我使用了@synchronized来保证这个结果数组的线程安全。
GPT给出的优化:
在先前的程序之中,我并没有对网络申请的data进行判空,导致获得的数据可能为空。而且使用@synchronized会导致线程开销较大,GPT此处给出了用栅栏函数,dispatch_barrier_async 会在所有并行任务完成后,独占执行添加数据的操作 ,这比 @synchronized
更高效。
objc
NSArray* fileUrls = @[@"https://example.com/file1.txt",
@"https://example.com/file2.txt",
@"https://example.com/file3.txt"];
NSMutableArray* downloadedFiles = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.text.GCDTest", DISPATCH_QUEUE_CONCURRENT);
// 使用并发队列控制对 downloadedFiles 的操作
dispatch_queue_t fileAccessQueue = dispatch_queue_create("com.example.fileAccessQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSString *str in fileUrls) {
dispatch_group_enter(group);
NSURL *url = [NSURL URLWithString:str];
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
dispatch_barrier_async(fileAccessQueue, ^{
[downloadedFiles addObject:data];
});
} else {
NSLog(@"下载失败,URL: %@", str);
}
dispatch_group_leave(group);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成");
NSLog(@"下载的文件数量: %lu", (unsigned long)downloadedFiles.count);
});
dispatch_main();
问题八:队列中任务的延迟执行
问题描述: 你需要在队列中添加一些任务,并且要求:
- 第一个任务立即执行。
- 第二个任务在 2 秒后执行。
- 第三个任务在 5 秒后执行。
要求:
- 使用
dispatch_after
来安排任务的延迟执行。
我写的代码:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"任务1正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
NSLog(@"任务2正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"任务3正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_main();
GPT给出的优化:
GPT一共给出两种优化,一种是使用嵌套进行组合控制依赖,即将下一个任务用dispatch_after
写在任务1之中
objc
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 任务1
dispatch_async(queue, ^{
NSLog(@"任务1正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
// 任务2 延迟 2 秒后执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
NSLog(@"任务2正在线程 %@ 执行", [NSThread currentThread]);
// 任务3 延迟 3 秒后执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
NSLog(@"任务3正在线程 %@ 执行", [NSThread currentThread]);
});
});
});
// 保持主线程不退出
dispatch_main();
另一种是使用dispatch_block_t
进行依赖操作
objc
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_block_t task1 = dispatch_block_create(0, ^{
NSLog(@"任务1正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_block_t task2 = dispatch_block_create(0, ^{
NSLog(@"任务2正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_block_t task3 = dispatch_block_create(0, ^{
NSLog(@"任务3正在线程 %@ 执行", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
// 依赖任务
dispatch_async(queue, task1);
dispatch_block_notify(task1, queue, task2);
dispatch_block_notify(task2, queue, task3);
dispatch_main();
问题九:在后台线程中同时执行多个任务并在主线程中汇总结果
问题描述: 你需要在后台线程中执行多个耗时的计算任务(如下载多个文件或处理数据),然后将结果汇总并更新 UI。要求:
- 在后台线程中并行执行多个任务。
- 等所有任务完成后,汇总结果并在主线程中更新 UI。
我写的代码:
objc
NSArray *tasks = @[@"task1",@"task2",@"task3",@"task4"];
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSMutableArray *results = [NSMutableArray array];
for(NSString *task in tasks) {
dispatch_group_enter(group);
dispatch_async(concurrentQueue, ^{
NSLog(@"开始执行 %@", task);
[NSThread sleepForTimeInterval:1];
NSString *result = [NSString stringWithFormat:@"%@ - 结果", task];
@synchronized (results) {
[results addObject:result];
}
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"🎉 所有任务完成,结果为: %@", results);
});
// 保持主线程不退出,等待任务完成
[[NSRunLoop currentRunLoop] run];
关于并发操作的添加数组的问题,GPT有时候使用锁,有时用的是栅栏函数,虽说本质上都是锁,但是有什么区别呢,我问了GPT,得到以下答案
对比项 | @synchronized / NSLock (锁) |
dispatch_barrier_async (栅栏函数) |
---|---|---|
适用场景 | 频繁的读写交替操作 ,需要同时锁住读和写 | 需要高频读取,少量写入的场景(读多写少) |
并行读取 | ❌ 读操作也会被阻塞,性能低 | ✅ 读操作不被阻塞,性能高 |
写操作 | ✅ 只有一个线程能写入,其他读/写被阻塞 | ✅ 只阻塞写入操作,不影响并行读取 |
性能 | ⛔️ 低性能,尤其是频繁读操作 | 🚀 高性能,大幅减少阻塞操作 |
实现难度 | 👍 简单,容易理解 | ⚠️ 略微复杂,仅限并发队列中使用 |
可用场景 | 串行任务、非 GCD 任务、锁定一切 | GCD 并发任务,写操作较少的场景 |
问题十:使用 GCD 实现懒加载
问题描述: 你需要实现一个懒加载功能,当需要时才开始加载数据。具体要求:
- 只有在用户滑动到某个位置时才开始加载数据。
- 数据加载完后,更新 UI(例如显示加载的图片)。
提示: 使用 dispatch_async
来延迟加载,并保证 UI 更新在主线程中。
我写的代码:
其实这个功能有点像知乎日报之中的,下拉加载新闻的内容,只有当tableView被下滑到底端才会进行加载,不过这个加载多了个占位符的图片的功能对我有点启发
objc
#import "ViewController.h"
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *imageUrls; // 模拟的图片URL数组
@property (nonatomic, strong) NSMutableDictionary *imageCache; // 图片缓存,避免重复加载
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.imageUrls = @[
@"https://via.placeholder.com/150/FF0000",
@"https://via.placeholder.com/150/00FF00",
@"https://via.placeholder.com/150/0000FF",
@"https://via.placeholder.com/150/FFFF00",
@"https://via.placeholder.com/150/FF00FF",
@"https://via.placeholder.com/150/00FFFF",
@"https://via.placeholder.com/150/000000",
@"https://via.placeholder.com/150/888888",
@"https://via.placeholder.com/150/123456",
@"https://via.placeholder.com/150/654321"
];
self.imageCache = [NSMutableDictionary dictionary]; // 图片缓存
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.imageUrls.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
// 显示占位符
cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];
// 获取当前的图片 URL
NSString *imageUrl = self.imageUrls[indexPath.row];
// 如果缓存中有图片,直接从缓存加载
if (self.imageCache[imageUrl]) {
NSLog(@"✅ 从缓存中加载图片: %@", imageUrl);
cell.imageView.image = self.imageCache[imageUrl];
} else {
// 只有当 Cell 可见时,才开始加载图片
[self lazyLoadImageForCell:cell atIndexPath:indexPath];
}
return cell;
}
#pragma mark - 滑动结束后触发懒加载
- (void)lazyLoadImageForCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSString *imageUrl = self.imageUrls[indexPath.row];
// 异步加载图片,防止阻塞主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"🚀 开始加载图片: %@", imageUrl);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
if (imageData) {
UIImage *image = [UIImage imageWithData:imageData];
// 加入缓存,避免重复加载
@synchronized (self) {
self.imageCache[imageUrl] = image;
}
// 回到主线程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *updateCell = [self.tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = image;
[updateCell setNeedsLayout];
}
});
} else {
NSLog(@"❌ 图片加载失败: %@", imageUrl);
}
});
}
@end
总结
GCD的使用确实是多线程的一个难点,通过完成以上的任务,我更深刻的掌握了GCD的相关用法,希望能对寒假的项目产生帮助