iOS 使用 NSBlockOperation 限制异步 API 调用
iOS throttling async API calls using NSBlockOperation
我想将进行中的 API 调用次数限制为 2。我可以创建一个 NSOperationQueue
并将块添加到队列中,但是每个 API 调用都有一个完成块,所以初始调用是有限的,但我不知道如何根据完成块的执行来限制队列的处理。
在下面的代码中,任何时候都可能有超过 2 个调用 API 正在运行。
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
service.requestQueue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 100; i++)
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[self invokeAPI:kAPIName completion:^BOOL(APIResult *result) {
// Do stuff
}
[requestQueue addOperation:operation];
}
}
非常感谢任何指向正确使用模式的指示。
编辑 - 基于 Marc-Alexandre 的回答
已经创建了这个 class 来封装操作,从内存的角度来看,这种方法是安全的,因为这个 class 将从 dataAccessService 创建并注入,以及完成块引用了自身,并且完成调用 before 完成块执行?
@interface MAGApiOperation : NSOperation
@property (nonatomic, strong) id<MAGDataAccessServiceProtocol> dataAccessService;
@property (nonatomic, copy) NSString *apiName;
@property (nonatomic, copy) BOOL (^onCompletion)(APIResult *);
+ (instancetype)apiOperationWithName:(NSString *)apiName dataAccessService:(id<MAGDataAccessServiceProtocol>)dataAccessService completion:(BOOL (^)(APIResult *))onCompletion;
@implementation MAGApiOperation
@synthesize executing = _isExecuting;
@synthesize finished = _isFinished;
#pragma mark - Class methods
/// Creates a new instance of MAGApiOperation
+ (instancetype)apiOperationWithName:(NSString *)apiName dataAccessService:(id<MAGDataAccessServiceProtocol>)dataAccessService completion:(BOOL (^)(APIResult *))onCompletion {
MAGApiOperation *operation = [[self alloc] init];
operation.apiName = apiName;
operation.dataAccessService = dataAccessService;
operation.onCompletion = onCompletion;
return operation;
}
#pragma mark - NSOperation method overrides
- (void)start {
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (!self.isCancelled)
{
[self invokeApiWithName:self.apiName completion:self.onCompletion];
}
}
- (void)finish {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
#pragma mark - Private methods
/// Invokes the api with the name then executes the completion block
- (void)invokeApiWithName:(NSString *)apiName completion:(BOOL (^)(VAAInvokeAPIResult *))onCompletion {
[self.dataAccessService invokeAPI:kAPIName completion:^BOOL(APIResult *result) { {
[self finish];
return onCompletion(result);
}];
}
您需要子类化 NSOperation 才能做到这一点。
这里是完整的文档,解释了如何将 NSOperation 子类化:https://developer.apple.com/reference/foundation/operation
快速笔记:
- 您的 "start" 操作将是
invokeAPI
调用。
- 然后在您的 invokeAPI 完成块中,您将自己的操作标记为已完成(见下文,注意 willChange 和 didChange 调用非常重要)
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self.isExecuting = NO;
self.isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
我想将进行中的 API 调用次数限制为 2。我可以创建一个 NSOperationQueue
并将块添加到队列中,但是每个 API 调用都有一个完成块,所以初始调用是有限的,但我不知道如何根据完成块的执行来限制队列的处理。
在下面的代码中,任何时候都可能有超过 2 个调用 API 正在运行。
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
service.requestQueue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 100; i++)
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[self invokeAPI:kAPIName completion:^BOOL(APIResult *result) {
// Do stuff
}
[requestQueue addOperation:operation];
}
}
非常感谢任何指向正确使用模式的指示。
编辑 - 基于 Marc-Alexandre 的回答
已经创建了这个 class 来封装操作,从内存的角度来看,这种方法是安全的,因为这个 class 将从 dataAccessService 创建并注入,以及完成块引用了自身,并且完成调用 before 完成块执行?
@interface MAGApiOperation : NSOperation
@property (nonatomic, strong) id<MAGDataAccessServiceProtocol> dataAccessService;
@property (nonatomic, copy) NSString *apiName;
@property (nonatomic, copy) BOOL (^onCompletion)(APIResult *);
+ (instancetype)apiOperationWithName:(NSString *)apiName dataAccessService:(id<MAGDataAccessServiceProtocol>)dataAccessService completion:(BOOL (^)(APIResult *))onCompletion;
@implementation MAGApiOperation
@synthesize executing = _isExecuting;
@synthesize finished = _isFinished;
#pragma mark - Class methods
/// Creates a new instance of MAGApiOperation
+ (instancetype)apiOperationWithName:(NSString *)apiName dataAccessService:(id<MAGDataAccessServiceProtocol>)dataAccessService completion:(BOOL (^)(APIResult *))onCompletion {
MAGApiOperation *operation = [[self alloc] init];
operation.apiName = apiName;
operation.dataAccessService = dataAccessService;
operation.onCompletion = onCompletion;
return operation;
}
#pragma mark - NSOperation method overrides
- (void)start {
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (!self.isCancelled)
{
[self invokeApiWithName:self.apiName completion:self.onCompletion];
}
}
- (void)finish {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
#pragma mark - Private methods
/// Invokes the api with the name then executes the completion block
- (void)invokeApiWithName:(NSString *)apiName completion:(BOOL (^)(VAAInvokeAPIResult *))onCompletion {
[self.dataAccessService invokeAPI:kAPIName completion:^BOOL(APIResult *result) { {
[self finish];
return onCompletion(result);
}];
}
您需要子类化 NSOperation 才能做到这一点。
这里是完整的文档,解释了如何将 NSOperation 子类化:https://developer.apple.com/reference/foundation/operation
快速笔记:
- 您的 "start" 操作将是
invokeAPI
调用。 - 然后在您的 invokeAPI 完成块中,您将自己的操作标记为已完成(见下文,注意 willChange 和 didChange 调用非常重要)
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self.isExecuting = NO;
self.isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];