MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中异步网络请求的并发控制策略

2023-10-211.6k 阅读

一、Objective-C 异步网络请求概述

在现代应用开发中,网络请求是不可或缺的一部分。Objective-C 作为苹果公司开发的编程语言,为开发者提供了丰富的工具和框架来处理网络请求。异步网络请求允许应用在等待网络响应时继续执行其他任务,从而提升用户体验。然而,当同时发起多个异步网络请求时,就需要对并发进行有效控制,以避免出现资源竞争、数据不一致等问题。

1.1 常用网络请求框架

  1. NSURLSession:这是 iOS 7.0 及以后引入的用于处理网络请求的框架。它提供了简洁的 API,支持 HTTP、HTTPS、FTP 等多种协议。NSURLSession 有三种类型的会话:NSURLSessionConfiguration 决定了会话的行为,NSURLSessionDataTask 用于获取数据,NSURLSessionUploadTask 用于上传数据,NSURLSessionDownloadTask 用于下载文件。例如,以下代码展示了如何使用 NSURLSessionDataTask 进行简单的 GET 请求:
NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        NSLog(@"请求出错: %@", error);
    } else {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"响应数据: %@", responseString);
    }
}];
[task resume];
  1. AFNetworking:这是一个广泛使用的第三方网络请求框架,它对 NSURLSession 进行了封装,提供了更便捷、更强大的功能。AFNetworking 支持多种请求方式(GET、POST、PUT、DELETE 等),并且对请求和响应的数据格式处理非常灵活,支持 JSON、XML、HTTP 等多种格式。以下是使用 AFNetworking 进行 POST 请求的示例:
#import <AFNetworking/AFNetworking.h>
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{@"key1": @"value1", @"key2": @"value2"};
[manager POST:@"https://example.com/api/data" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"成功: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"失败: %@", error);
}];
  1. Alamofire:也是一个流行的第三方网络框架,它在 AFNetworking 的基础上进一步简化了网络请求的操作。Alamofire 提供了链式调用的语法,使得代码更加简洁易读。例如,使用 Alamofire 进行 GET 请求:
#import <Alamofire/Alamofire.h>
[Alamofire.request(@"https://example.com/api/data").responseJSON { response in
    if (response.result.isSuccess) {
        NSLog(@"成功: %@", response.result.value);
    } else {
        NSLog(@"失败: %@", response.result.error);
    }
}];

二、并发控制的必要性

当应用同时发起多个异步网络请求时,如果不进行并发控制,可能会出现以下问题:

2.1 资源竞争

多个网络请求可能同时访问和修改共享资源,例如内存中的数据结构。假设一个应用在下载多个文件时,这些下载任务都要更新一个用于显示下载进度的全局变量。如果没有并发控制,可能会导致该变量的值被错误地更新,从而显示出不正确的下载进度。

2.2 性能问题

过多的并发请求可能会耗尽系统资源,如网络带宽、内存等。这会导致应用性能下降,甚至出现卡顿现象。例如,同时发起大量的图片下载请求,可能会使网络拥堵,导致其他重要的网络请求无法及时完成。

2.3 数据一致性

在一些场景下,多个网络请求之间可能存在依赖关系。例如,一个请求获取用户信息,另一个请求根据用户信息获取用户的订单列表。如果这些请求没有按照正确的顺序执行,可能会导致显示的数据不一致。

三、并发控制策略

3.1 队列控制

  1. NSOperationQueueNSOperationQueue 是 Objective-C 中用于管理异步任务的队列。通过创建 NSOperation 对象并将其添加到队列中,可以实现任务的并发执行。同时,NSOperationQueue 提供了控制并发数量的方法。例如,以下代码展示了如何使用 NSOperationQueue 控制并发请求数量:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3; // 设置最大并发数为 3
for (int i = 0; i < 10; i++) {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"请求出错: %@", error);
            } else {
                NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"响应数据: %@", responseString);
            }
        }];
        [task resume];
    }];
    [queue addOperation:operation];
}
  1. AFURLSessionManager 的队列控制:AFNetworking 中的 AFURLSessionManager 也可以通过设置 NSOperationQueue 来控制并发。例如:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 2;
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
manager.operationQueue = operationQueue;
for (int i = 0; i < 5; i++) {
    [manager GET:@"https://example.com/api/data" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"成功: %@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"失败: %@", error);
    }];
}

3.2 信号量控制

  1. 使用 dispatch_semaphore:GCD(Grand Central Dispatch)中的 dispatch_semaphore 可以用于控制并发访问。信号量的值表示当前可用的资源数量。当信号量的值为 0 时,尝试获取信号量的线程会被阻塞,直到信号量的值大于 0。以下是使用 dispatch_semaphore 控制网络请求并发的示例:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3); // 创建信号量,初始值为 3
for (int i = 0; i < 10; i++) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"请求出错: %@", error);
            } else {
                NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"响应数据: %@", responseString);
            }
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];
    });
}
  1. 在 AFNetworking 中结合信号量:可以在 AFNetworking 的请求回调中使用信号量来控制并发。例如:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
for (int i = 0; i < 5; i++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [manager GET:@"https://example.com/api/data" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"成功: %@", responseObject);
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"失败: %@", error);
        dispatch_semaphore_signal(semaphore);
    }];
}

3.3 依赖关系控制

  1. NSOperation 的依赖关系NSOperation 可以设置依赖关系,确保某些任务在其他任务完成后才执行。例如,假设有两个网络请求任务 operation1operation2,需要 operation1 完成后再执行 operation2,可以这样设置:
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/user"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"请求出错: %@", error);
        } else {
            NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"用户信息响应数据: %@", responseString);
        }
    }];
    [task resume];
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/orders"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"请求出错: %@", error);
        } else {
            NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"订单信息响应数据: %@", responseString);
        }
    }];
    [task resume];
}];
[operation2 addDependency:operation1];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:NO];
  1. AFNetworking 中的依赖关系处理:虽然 AFNetworking 本身没有直接提供设置任务依赖关系的功能,但可以通过自定义逻辑来实现类似效果。例如,可以在第一个请求的成功回调中发起第二个请求,从而模拟依赖关系:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://example.com/api/user" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    // 第一个请求成功后发起第二个请求
    [manager GET:@"https://example.com/api/orders" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"订单信息成功: %@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"订单信息失败: %@", error);
    }];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"用户信息失败: %@", error);
}];

四、实际应用场景及优化

4.1 图片加载优化

在图片加载场景中,通常会有大量的图片需要从网络加载。如果同时发起过多的图片加载请求,会占用大量网络带宽,影响应用性能。可以使用队列控制并发数量,例如设置 NSOperationQueuemaxConcurrentOperationCount 为 5,即同时最多加载 5 张图片。

NSOperationQueue *imageQueue = [[NSOperationQueue alloc] init];
imageQueue.maxConcurrentOperationCount = 5;
NSArray *imageURLs = @[@"https://example.com/image1.jpg", @"https://example.com/image2.jpg", @"https://example.com/image3.jpg", @"https://example.com/image4.jpg", @"https://example.com/image5.jpg", @"https://example.com/image6.jpg"];
for (NSString *urlString in imageURLs) {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:urlString];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"图片加载出错: %@", error);
            } else {
                UIImage *image = [UIImage imageWithData:data];
                // 在主线程更新 UI 显示图片
                dispatch_async(dispatch_get_main_queue(), ^{
                    // 假设存在一个 UIImageView 用于显示图片
                    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
                    imageView.image = image;
                    [[UIApplication sharedApplication].keyWindow addSubview:imageView];
                });
            }
        }];
        [task resume];
    }];
    [imageQueue addOperation:operation];
}

4.2 数据批量请求优化

当需要从服务器获取大量数据时,可能需要发起多个请求来获取不同部分的数据。例如,一个电商应用需要获取商品列表、商品详情、用户评价等数据。可以通过依赖关系控制,先获取商品列表,然后根据商品列表中的商品 ID 获取商品详情,最后获取用户评价。这样可以确保数据的一致性,并且避免不必要的请求。

// 获取商品列表
NSBlockOperation *productListOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/products"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"商品列表请求出错: %@", error);
        } else {
            NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSLog(@"商品列表响应数据: %@", responseDict);
        }
    }];
    [task resume];
}];
// 获取商品详情,依赖商品列表请求完成
NSBlockOperation *productDetailOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 假设从商品列表响应数据中获取商品 ID 来请求商品详情
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/product/1"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"商品详情请求出错: %@", error);
        } else {
            NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSLog(@"商品详情响应数据: %@", responseDict);
        }
    }];
    [task resume];
}];
[productDetailOperation addDependency:productListOperation];
// 获取用户评价,依赖商品详情请求完成
NSBlockOperation *productReviewOperation = [NSBlockOperation blockOperationWithBlock:^{
    // 假设从商品详情响应数据中获取商品 ID 来请求用户评价
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/product/1/reviews"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"用户评价请求出错: %@", error);
        } else {
            NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSLog(@"用户评价响应数据: %@", responseDict);
        }
    }];
    [task resume];
}];
[productReviewOperation addDependency:productDetailOperation];
NSOperationQueue *dataQueue = [[NSOperationQueue alloc] init];
[dataQueue addOperations:@[productListOperation, productDetailOperation, productReviewOperation] waitUntilFinished:NO];

4.3 网络状态监测与并发调整

在实际应用中,网络状态可能会发生变化。可以使用 Reachability 类来监测网络状态。当网络状态从良好变为较差时,可以适当减少并发请求数量,以避免网络拥堵。例如:

#import "Reachability.h"
Reachability *reachability = [Reachability reachabilityForInternetConnection];
[reachability startNotifier];
reachability.reachableBlock = ^(Reachability *reachability) {
    NetworkStatus status = [reachability currentReachabilityStatus];
    if (status == ReachableViaWiFi || status == ReachableViaWWAN) {
        // 网络良好,设置较高的并发数
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 5;
    } else {
        // 网络较差,设置较低的并发数
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 2;
    }
};
reachability.unreachableBlock = ^(Reachability *reachability) {
    // 网络不可用,暂停所有网络请求
    NSArray *operations = [queue operations];
    for (NSOperation *operation in operations) {
        [operation cancel];
    }
};

五、错误处理与并发控制的结合

5.1 单个请求错误处理

在异步网络请求中,每个请求都可能出现错误。例如,网络连接失败、服务器响应错误等。在使用 NSURLSession 时,错误信息会在 completionHandlerNSError 参数中返回。对于并发请求,需要在每个请求的错误处理中考虑对整个并发任务的影响。例如,如果一个请求失败,是否需要取消其他相关请求。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"请求出错: %@", error);
                // 这里可以选择取消其他相关请求
                NSArray *operations = [queue operations];
                for (NSOperation *op in operations) {
                    if (op != operation) {
                        [op cancel];
                    }
                }
            } else {
                NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"响应数据: %@", responseString);
            }
        }];
        [task resume];
    }];
    [queue addOperation:operation];
}

5.2 全局错误处理

在应用中,可以设置全局的错误处理机制,特别是在使用第三方网络框架时。例如,AFNetworking 可以通过设置 AFHTTPRequestOperationManagerresponseSerializer 来统一处理错误。同时,在并发请求场景下,全局错误处理需要与并发控制策略相结合,确保错误处理不会影响其他正常请求的执行。

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
manager.requestSerializer.timeoutInterval = 15;
[manager setCompletionQueue:nil];
[manager setSecurityPolicy:[AFSecurityPolicy defaultPolicy]];
[manager POST:@"https://example.com/api/data" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"成功: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"失败: %@", error);
    // 全局错误处理,可以根据错误类型决定是否取消其他请求
    if (error.code == -1009) { // 网络连接失败错误码
        NSArray *operations = [manager.operationQueue operations];
        for (NSOperation *operation in operations) {
            if (!operation.isFinished &&!operation.isCancelled) {
                [operation cancel];
            }
        }
    }
}];

通过合理运用这些并发控制策略,并结合实际应用场景进行优化和错误处理,可以有效地提升 Objective-C 应用中异步网络请求的性能和稳定性。