NSOperation 子类性能或泄漏

NSOperation Subclass performance or leak

下面是NSOperation子类的子类实现 该操作将用于从服务器异步下载图像。

-(void) main{

    @autoreleasepool {
      //NSURLConnection
        NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"A URl"]];
        _downloadConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
    }
}

-(BOOL)isConcurrent{
    return YES;
}

-(BOOL)isExecuting{
    return _isExecuting;
}

-(BOOL)isFinished{
    return _isFinished;
}

-(void)finish{

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    _isExecuting = NO;
    _isFinished  =YES;

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
}

-(void)cancel{

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    _isExecuting = NO;
    _isFinished  =YES;

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
}


-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    [connection cancel];
    connection=nil;
    [self finish];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //Other Code
    [connection cancel];
    connection=nil;
    [self finish];
}

如果我在代码中遗漏了任何内容,请告诉我,以避免泄漏并检查所有 KVO 是否已正确处理。

我发现了几个问题:

  1. 您的 finishcancel 例程为每个键调用 willChangeValueForKey 两次。显然,第二次调用应该是 didChangeValueForKey.

  2. 我建议不要实施 cancel 方法。默认实现做了一些其他的事情。不要实施该方法。如果你真的想要,我会在这里建议一些改变(至少,也取消连接;调用 super;还有一些其他的事情),但我只是建议不要实施它并且在 didReceiveData 中检测取消(参见第 5 点)。

  3. 当操作开始时,这段代码似乎没有设置 _isExecuting(也没有适当的 KVO)。也许这是您忘记与我们分享的 start 方法?

  4. 同样,start 方法应该检查操作是否已经被取消,如果是,立即停止操作。

  5. didReceiveData中,您是否也检查isCancelled?使操作可取消是您使用操作队列的主要原因之一。

  6. 您正在操作中启动 NSURLConnection(可能是为了将此操作添加到某个随机队列)。但是 NSURLConnection 将无法正常工作,除非你将它安排在主 运行 循环(简单的解决方案)或者你创建自己的 运行 循环(有多种技术).

    例如,要告诉操作在主 运行 循环中安排连接,您可以这样做:

    _downloadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:FALSE];
    [_downloadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] runLoopModes:NSRunLoopCommonModes];
    [_downloadConnection start];
    

    我输入的内容没有 Xcode 的好处,所以如果我输入错误的方法请原谅我,但它说明了这个想法:与 FALSEstartImmediately 建立连接,安排它在主 运行 循环中,然后你才应该 start 它。

  7. 如果调用了connectionDidFinishLoading,完全没有必要调用[connection cancel].

  8. 自 iOS 7 起,isConcurrent 已被弃用,取而代之的是 isAsynchronous。但是,如果您需要支持早期的 iOS 版本,请保留 isConcurrent.

  9. 顺便说一句,虽然我认为它可能会按照您的布局方式工作,但通常建议实现名为 executingfinished 的属性:

     @property (nonatomic, readwrite, getter=isExecuting) BOOL executing;
     @property (nonatomic, readwrite, getter=isFinished)  BOOL finished;
    

    然后我手动合成:

     @synthesize finished  = _finished;
     @synthesize executing = _executing;
    

    然后我实现手动设置器(但依赖合成的 getter 和 ivar):

    - (void)setExecuting:(BOOL)executing
    {
        if (_executing != executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
    
    - (void)setFinished:(BOOL)finished
    {
        if (_finished != finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
    

    但是如果你这样做,你现在可以设置 self.executing = FALSE(或其他)并且它 (a) 做适当的 KVO,避免你的代码被各种 KVO 调用弄得乱七八糟;和 (b) 但让您不必手动实现属性和吸气剂。