「OC」GCD的相关练习

「OC」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:任务完成后的顺序执行

问题描述: 你需要在后台异步执行三个任务,确保任务按顺序完成,并且每个任务的执行都依赖前一个任务的完成结果。例如:

  1. 第一个任务:下载数据。
  2. 第二个任务:处理数据。
  3. 第三个任务:展示数据。

要求:

  • 使用 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_afterdispatch_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(显示在一个图片画廊中)。要求:

  1. 使用 dispatch_async 来加载每张图片。
  2. 确保 UI 更新发生在主线程。
  3. 加载完成后,显示一个提示"图片加载完成"。

提示: 可以使用 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,例如显示图片
});

为了确保图片下载的顺序与任务的发起顺序一致,为每个下载任务分配一个索引,确保图片按照该索引顺序被存储。使用一个数组,保存图片的下载顺序。每次下载成功后,将图片放入正确的位置。

问题五:多个任务的优先级控制

问题描述: 你需要执行一系列任务,其中:

  1. 一些任务优先级较高,需要尽快完成。
  2. 其他任务可以延迟执行。 要求:
  • 使用 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。具体要求:

  1. 假设你有一个包含多个文件 URL 的数组。
  2. 对每个文件 URL 进行异步下载。
  3. 当所有文件下载完成后,更新 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。要求:

  1. 在后台线程中并行执行多个任务。
  2. 等所有任务完成后,汇总结果并在主线程中更新 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的相关用法,希望能对寒假的项目产生帮助

相关推荐
胖虎17 小时前
实现 iOS 自定义高斯模糊文字效果的 UILabel(文末有Demo)
ios·高斯模糊文字·模糊文字
_可乐无糖2 天前
Appium 检查安装的驱动
android·ui·ios·appium·自动化
胖虎12 天前
iOS 网络请求: Alamofire 结合 ObjectMapper 实现自动解析
ios·alamofire·objectmapper·网络请求自动解析·数据自动解析模型
开发者如是说2 天前
破茧英语路:我的经验与自研软件
ios·创业·推广
假装自己很用心2 天前
iOS 内购接入StoreKit2 及低与iOS 15 版本StoreKit 1 兼容方案实现
ios·swift·storekit·storekit2
iOS阿玮3 天前
“小红书”海外版正式更名“ rednote”,突然爆红的背后带给开发者哪些思考?
ios·app·apple
刘小哈哈哈3 天前
iOS UIScrollView的一个特性
macos·ios·cocoa
忆江南的博客4 天前
iOS 性能优化:实战案例分享
ios
忆江南的博客4 天前
深入剖析iOS网络优化策略,提升App性能
ios
一丝晨光5 天前
GCC支持Objective C的故事?Objective-C?GCC只能编译C语言吗?Objective-C 1.0和2.0有什么区别?
c语言·开发语言·ios·objective-c·msvc·clang·gcc