MST

星途 面试题库

面试题:Objective-C中GCD实现异步网络请求并发控制的优化策略

在使用Grand Central Dispatch(GCD)进行异步网络请求并发控制时,如何通过设置队列优先级、任务依赖关系等方式优化并发性能,避免资源竞争和死锁问题?请详细阐述实现思路并提供代码示例。
11.5万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 队列优先级
    • GCD 提供了不同优先级的队列,包括 DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND。对于网络请求,重要且需要快速响应的请求可以放在高优先级队列,而一些次要的请求可以放在低优先级队列。
    • 例如,用户登录相关的网络请求,涉及到用户身份验证等关键操作,可以使用高优先级队列,以确保尽快得到响应。而一些用于获取缓存更新的请求,对及时性要求不高,可以放在低优先级队列。
  2. 任务依赖关系
    • 可以通过 dispatch_group_enterdispatch_group_leave 函数来设置任务之间的依赖关系。当一个任务依赖于其他多个任务完成后才能执行时,使用 dispatch_group 可以方便地实现这种依赖。
    • 例如,在一个应用中,可能需要先获取用户基本信息,再根据用户信息获取用户的详细配置,后面这个获取详细配置的任务就依赖于获取用户基本信息的任务完成。
  3. 避免资源竞争和死锁
    • 资源竞争
      • 对于共享资源,使用串行队列来访问,确保同一时间只有一个任务能访问共享资源。例如,在多个网络请求可能会修改同一个本地数据库时,将数据库操作放在一个串行队列中执行。
      • 也可以使用 dispatch_semaphore 信号量来控制对共享资源的访问,信号量的值表示可以同时访问资源的任务数量。
    • 死锁
      • 避免在持有锁(或类似的同步机制)的情况下尝试获取相同或更高层次的锁。在 GCD 中,避免在一个队列中同步等待另一个队列中任务的完成,特别是当这两个队列存在相互依赖关系时。

代码示例

#import <Foundation/Foundation.h>

// 示例网络请求函数,这里简单模拟网络请求
void networkRequestWithURL(NSURL *url, void (^completion)(NSData *data, NSError *error)) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = [NSData dataWithContentsOfURL:url];
        NSError *error = nil;
        if (!data) {
            error = [NSError errorWithDomain:@"NetworkError" code:1 userInfo:nil];
        }
        completion(data, error);
    });
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建一个并发队列
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
        // 创建一个串行队列用于访问共享资源(模拟)
        dispatch_queue_t serialQueueForSharedResource = dispatch_queue_create("com.example.serialQueueForSharedResource", DISPATCH_QUEUE_SERIAL);
        
        // 创建一个 dispatch_group
        dispatch_group_t group = dispatch_group_create();
        
        // 第一个网络请求
        NSURL *url1 = [NSURL URLWithString:@"https://example.com/api/userInfo"];
        dispatch_group_enter(group);
        dispatch_async(concurrentQueue, ^{
            __block NSData *userInfoData;
            __block NSError *userInfoError;
            networkRequestWithURL(url1, ^(NSData *data, NSError *error) {
                userInfoData = data;
                userInfoError = error;
                dispatch_group_leave(group);
            });
        });
        
        // 第二个网络请求,依赖第一个网络请求完成
        NSURL *url2 = [NSURL URLWithString:@"https://example.com/api/userConfig"];
        dispatch_group_enter(group);
        dispatch_group_notify(group, concurrentQueue, ^{
            if (!userInfoError) {
                // 这里根据 userInfoData 构造 url2 的请求参数等操作
                networkRequestWithURL(url2, ^(NSData *data, NSError *error) {
                    // 处理第二个请求的结果
                    dispatch_async(serialQueueForSharedResource, ^{
                        // 这里访问共享资源,例如将数据保存到本地数据库
                    });
                    dispatch_group_leave(group);
                });
            } else {
                dispatch_group_leave(group);
            }
        });
        
        // 等待所有任务完成
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"All network requests are completed.");
    }
    return 0;
}

上述代码展示了如何使用 GCD 进行异步网络请求的并发控制,通过设置任务依赖关系以及使用串行队列避免资源竞争。同时,利用 dispatch_group 来管理多个任务的执行流程。在实际应用中,networkRequestWithURL 函数会替换为真实的网络请求代码,并且共享资源的操作会根据具体需求进行实现。