首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSOperation + setCompletionBlock

NSOperation + setCompletionBlock
EN

Stack Overflow用户
提问于 2014-04-12 17:22:04
回答 3查看 1.4K关注 0票数 1

关于NSOperationNSOperationQueue,我有几个不同的问题,我知道你们的回答会帮助我;

我必须加载大量的映像,并且我已经基于NSOperationNSOperationQueueNSURLConnection (异步加载)创建了自己的加载程序;

问题:

  1. 如果我将maxConcurrentOperationCount (例如3)设置为queue (NSOperationQueue),这是否意味着只有3个同时执行的操作--即使队列有100个操作?
  2. 当我将属性maxConcurrentOperationCount设置为队列时,有时"setCompletionBlock“不工作和计数(operationCount)只会增加;为什么?

MyLoader:

代码语言:javascript
复制
- (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);
        }];
    }
}

MyOperation:

代码语言:javascript
复制
@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"];
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-04-13 13:42:46

必须在操作的start方法中,而不是在fileServerUrl:handler:中启动连接。

我将完全删除此方法,并且只提供一个init方法,其中包含所有必需的参数,您可以在其中完全设置操作。然后,在方法start中启动连接。

此外,还不清楚为什么要覆盖main

修改状态变量_executing_finished可能更简洁、更清晰(您不需要最初设置它们,因为已经初始化为NO)。只在“最终”方法completeOperation中设置它们,包括KVO通知。

您也不需要在stop中使用@try/@ dispatch_async(),因为函数dispatch_async()不会抛出Objective异常。

您的cancel方法不是线程安全的,还有其他一些问题。我建议作以下修改:

代码语言:javascript
复制
@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];
}
票数 1
EN

Stack Overflow用户

发布于 2014-04-12 20:40:57

在回答你的问题时:

  1. 是的,3的maxConcurrentOperationCount意味着一次只运行3次。执行这样的网络请求是您想要使用maxConcurrentOperationCount的最佳例子,因为如果不这样做,将导致太多的网络请求试图运行,很可能导致一些连接在使用较慢的网络连接时失败。

  1. 但是,这里的主要问题是,您正在从fileServerUrl调用操作的MyLoader方法(该方法正在启动连接)。您已经将请求与操作的start断开连接(这违背了maxConcurrentCount of 3的目的,并可能混淆了操作的状态)。 start方法应该启动连接(即,在这三个可用的并发操作中有一个可用之前,不要启动请求)。此外,由于不能将URL和handler传递给start方法,所以应该将保存这些值的逻辑移动到init方法的自定义呈现。

我们可能会向您的操作建议其他一些小编辑(不需要mainoperationStarted有点冗余,简化_executing/_finished处理等等),但关键问题是在fileServerUrl中启动连接,而不是由start方法启动。

因此:

代码语言:javascript
复制
- (id)initWithServerUrl:(NSString *)fileServerUrl
                handler:(void(^)(NSData *))handler
{
    self = [super init];
    if (self) {
        _executing = NO;
        _finished = NO;

        // do your saving of `fileServerURL` and `handler` here, e.g.

        self.fileServerUrl = fileServerUrl;

        self.OnFinishLoading:^(NSData *loadData) {
            handler(loadData);
        }];

        [self setOnFailedLoading:^{
            handler(nil);
        }];
    }
    return self;
}

- (void)startRequest {
    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)start {
    if ([self isCancelled])
    {
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];

        return;
    }

    [self setOperationStarted:YES];  // personally, I'd retire this and just reference your `executing` flag, but I'll keep it here for compatibility with the rest of your code

    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

    [self startRequest];
}
票数 1
EN

Stack Overflow用户

发布于 2014-04-12 20:32:28

对于第一个问题,答案是肯定的,如果将3设为最大操作数,则只能运行3。

第二个问题有点奇怪,我不确定这个答案是否正确。

当您将操作留给NSOperationQueue时,您无法确定它们将在哪个线程上执行,这将导致异步连接的一个巨大问题。

当您像往常一样启动一个NSURLConnection时,您会收到委托回调,而不会出现问题,这是因为连接运行在一个线程上,并且运行了一个活动的run循环。如果在次要线程上启动连接,则将在该线程上调用回调,但如果不使run循环保持活动,则将永远不会收到回调。

这可能是我的答案不正确的地方,GCD应该负责动态运行循环,因为GCD队列运行在活动线程上。

但如果不是,问题可能是操作是在另一个线程上启动的,启动方法被调用,但是回调永远不会被调用。试着检查线程是否总是主线程。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23033707

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档