Ios NSO操作+;setCompletionBlock
关于Ios NSO操作+;setCompletionBlock,ios,objective-c,nsurlconnection,nsoperation,nsoperationqueue,Ios,Objective C,Nsurlconnection,Nsoperation,Nsoperationqueue,关于NSOperation和NSOperationQueue我有一些不同的问题,我知道你们的答案会对我有所帮助 我必须加载大量图像,并基于NSOperation、NSOperationQueue和NSURLConnection(异步加载)创建了自己的加载程序 问题: 如果我为队列(NSOperationQueue)设置了maxConcurrentOperationCount(例如3),这是否意味着同一时间仅执行3个操作,即使队列有100个操作 当我为队列设置属性maxConcurrentOper
NSOperation
和NSOperationQueue
我有一些不同的问题,我知道你们的答案会对我有所帮助
我必须加载大量图像,并基于NSOperation
、NSOperationQueue
和NSURLConnection
(异步加载)创建了自己的加载程序
问题:
NSOperationQueue
)设置了maxConcurrentOperationCount
(例如3),这是否意味着同一时间仅执行3个操作,即使队列有100个操作maxConcurrentOperationCount
时,有时“setCompletionBlock
”不起作用,计数(operationCount
)只会增加;为什么?- (id)init
{
self = [super init];
if (self) {
_loadingFiles = [NSMutableDictionary new];
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 3;
_downloadQueue.name = @"LOADER QUEUE";
}
return self;
}
- (void)loadFile:(NSString *)fileServerUrl handler:(GetFileDataHandler)handler {
if (fileServerUrl.length == 0) {
return;
}
if ([_loadingFiles objectForKey:fileServerUrl] == nil) {
[_loadingFiles setObject:fileServerUrl forKey:fileServerUrl];
__weak NSMutableDictionary *_loadingFiles_ = _loadingFiles;
MyLoadOperation *operation = [MyLoadOperation new];
[operation fileServerUrl:fileServerUrl handler:^(NSData *fileData) {
[_loadingFiles_ removeObjectForKey:fileServerUrl];
if (fileData != nil) {
handler(fileData);
}
}];
[operation setQueuePriority:NSOperationQueuePriorityLow];
[_downloadQueue addOperation:operation];
__weak NSOperationQueue *_downloadQueue_ = _downloadQueue;
[operation setCompletionBlock:^{
NSLog(@"completion block :%i", _downloadQueue_.operationCount);
}];
}
}
我的手术:
@interface MyLoadOperation()
@property (nonatomic, assign, getter=isOperationStarted) BOOL operationStarted;
@property(nonatomic, strong)NSString *fileServerUrl;
@property(nonatomic, copy)void (^OnFinishLoading)(NSData *);
@end
@implementation MyLoadOperation
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)fileServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler {
@autoreleasepool {
self.fileServerUrl = fileServerUrl;
[self setOnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:@"" forHTTPHeaderField:@"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
}
- (void)main {
@autoreleasepool {
[self stop];
}
}
- (void)start {
[self setOperationStarted:YES];
[self willChangeValueForKey:@"isFinished"];
_finished = NO;
[self didChangeValueForKey:@"isFinished"];
if ([self isCancelled])
{
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
_executing = NO;
[self didChangeValueForKey:@"isFinished"];
}
else
{
[self willChangeValueForKey:@"isExecuting"];
_finished = NO;
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
- (void)cancel {
[self.connection cancel];
if ([self isExecuting])
{
[self stop];
}
[super cancel];
}
#pragma mark -NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self OnFinishLoading]) {
[self OnFinishLoading](_data);
}
if (![self isCancelled]) {
[self stop];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
;
if (![self isCancelled]) {
[self stop];
}
}
- (void)stop {
@try {
__weak MyLoadOperation *self_ = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self_ completeOperation];
});
}
@catch (NSException *exception) {
NSLog(@"Exception! %@", exception);
[self completeOperation];
}
}
- (void)completeOperation {
if (![self isOperationStarted]) return;
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
对于第一个问题,答案是肯定的,如果将3设置为最大操作数,则只能同时运行3个。
第二个问题有点奇怪,我不能完全确定这个答案是否正确。
当您将操作留给NSOperationQueue时,您无法确定它们将在哪个线程上执行,这会给异步连接带来一个巨大的问题。
当您像往常一样启动NSURLConnection时,您会毫无问题地收到委托回调,这是因为连接运行在具有活动运行循环的线程上。如果在辅助线程上启动连接,将在该线程上调用回调,但如果不保持运行循环的活动状态,则将永远不会收到回调。
这就是我的答案可能不正确的地方,GCD应该处理活动运行循环,因为GCD队列在活动线程上运行
但如果不是,问题可能是操作在不同的线程上启动,调用start方法,但从不调用回调。尝试检查线程是否始终是主线程。在回答您的问题时:
maxConcurrentOperationCount
为三表示一次只运行三个。像这样执行网络请求是您希望使用maxConcurrentOperationCount
的最佳示例,因为如果不这样做,将导致尝试运行的网络请求过多,很可能会导致某些连接在使用较慢的网络连接时失败
MyLoader
调用操作的fileServerUrl
方法(启动连接)。您已将请求从操作的开始
断开(违背了3
的maxConcurrentCount
的目的,可能会混淆操作的状态)
start
方法应启动连接(即,在三个可用并发操作之一可用之前不要启动请求)。此外,由于无法将URL和处理程序
传递给start
方法,因此应将保存这些值的逻辑移动到init
方法的自定义格式副本中
main
不需要,operationStarted
有点多余,简化\u执行
/\u完成
处理等),但关键问题是在fileServerUrl
中启动连接,而不是由start
方法启动
因此:
您必须在操作的
start
方法中启动连接,而不是在fileServerUrl:handler:
中启动连接
我将完全删除这个方法,只提供一个init方法,其中包含所有必需的参数,您可以完全设置操作。然后,在方法start
中启动连接
此外,不清楚为什么要覆盖main
修改状态变量\u executing
和\u finished
可以更简洁、更清晰(您不需要最初设置它们,因为它们已经初始化为NO
)。仅在“最终”方法completeOperation
中设置它们,包括KVO通知
您也不需要在stop
中使用@try/@catch,因为函数dispatch\u async()
不会引发Objective-C异常
您的cancel
方法不是线程安全的,还有一些其他问题。我建议作出以下修改:
@implementation MyOperation {
BOOL _executing;
BOOL _finished;
NSError* _error; // remember the error
id _result; // the "result" of the connection, unless failed
completion_block_t _completionHandler; //(your own completion handler)
id _self; // strong reference to self
}
// Use the "main thread" as the "synchronization queue"
- (void) start
{
// Ensure start will be called only *once*:
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled && !_finished && !_executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
_self = self; // keep a strong reference to self in order to make
// the operation "immortal for the duration of the task
// Setup connection:
...
[self.connection start];
}
});
}
- (void) cancel
{
dispatch_async(dispatch_get_main_queue, ^{
[super cancel];
[self.connection cancel];
if (!_finished && !_executing) {
// if the op has been cancelled before we started the connection
// ensure the op will be orderly terminated:
self.error = [[NSError alloc] initWithDomain:@"MyOperation"
code:-1000
userInfo:@{NSLocalizedDescriptionKey: @"cancelled"}];
[self completeOperation];
}
});
}
- (void)completeOperation
{
[self willChangeValueForKey:@"isExecuting"];
self.isExecuting = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self.isFinished = YES;
[self didChangeValueForKey:@"isFinished"];
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = self.result;
NSError* error = self.error;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result, error);
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self onFinishLoading]) {
[self onFinishLoading](self.result);
}
[self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.error == nil) {
self.error = error;
}
[self completeOperation];
}
要明确的是,您是说当使用3的
maxConcurrentOperationCount
时,您看到所有100个操作都运行并完成了(不仅仅是前三个),但没有看到operationCount
根本没有变化?operationCount
方法并不完全可靠(请参阅该方法的文档),但您应该看到返回的值有一些变化。你能更详细地描述一下这种行为吗?谢谢你的回答;是的,你是对的,我必须更好地描述这种行为;看,如果我删除maxConcurrentOperationCount
上面显示的代码,一切都很好;精细-我的意思是每次调用setCompletionBlock
和operationCount
减少;如果我使用maxConcurrentOperationCount
(数字无关紧要)setCompletionBlock
调用,但它通常在我滚动表格视图时停止工作(我在其中显示加载图像);换句话说,我只更改一行代码,而代码更改行为;OP正在计划[nsrunlop main runloop]
上的NSURLConnection
委托,这解决了您描述的问题。我认为问题出在别处。谢谢你的回答;你给了我非常有用的建议;不幸的是,现在最大的问题是我无法停止执行操作。换句话说,方法cancel
从不调用;我不明白为什么
@implementation MyOperation {
BOOL _executing;
BOOL _finished;
NSError* _error; // remember the error
id _result; // the "result" of the connection, unless failed
completion_block_t _completionHandler; //(your own completion handler)
id _self; // strong reference to self
}
// Use the "main thread" as the "synchronization queue"
- (void) start
{
// Ensure start will be called only *once*:
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled && !_finished && !_executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
_self = self; // keep a strong reference to self in order to make
// the operation "immortal for the duration of the task
// Setup connection:
...
[self.connection start];
}
});
}
- (void) cancel
{
dispatch_async(dispatch_get_main_queue, ^{
[super cancel];
[self.connection cancel];
if (!_finished && !_executing) {
// if the op has been cancelled before we started the connection
// ensure the op will be orderly terminated:
self.error = [[NSError alloc] initWithDomain:@"MyOperation"
code:-1000
userInfo:@{NSLocalizedDescriptionKey: @"cancelled"}];
[self completeOperation];
}
});
}
- (void)completeOperation
{
[self willChangeValueForKey:@"isExecuting"];
self.isExecuting = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self.isFinished = YES;
[self didChangeValueForKey:@"isFinished"];
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = self.result;
NSError* error = self.error;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result, error);
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self onFinishLoading]) {
[self onFinishLoading](self.result);
}
[self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.error == nil) {
self.error = error;
}
[self completeOperation];
}