Search code examples
ioskey-value-observingnsoperationqueue

object of NSOperation doesn't removed from NSOperationQueue after executing


In my subclass of NSOperation I set 4 flags, and when an operation finishes its execution it is not removed to NSOperation queue, where it was added at the beginning, this thing cause a lot of issues in my app. I suppose that the way I set these flags is not correct, could you please help with it. cause I really spend a lot of time on identifying this issue.

@property(assign, nonatomic) BOOL isCancelled;
@property(nonatomic, getter=isExecuting) BOOL executing;
@property(nonatomic, getter=isFinished) BOOL finished;
@property(readonly, getter=isAsynchronous) BOOL asynchronous;

//in initialisation 
- (id)initWithURL:(NSURL*)url andRaw:(NSInteger)row
{
    if (![super init])
        return nil;
    [self setTargetURL:url];

    return self;
}
//the way I override KVO
- (BOOL)isExecuting
{
     NSLog(@"Exec");
    return (self.defaultSession != nil);//it doesn't work
}

- (BOOL)isFinished
{
    NSLog(@"Finished");
    return (self.defaultSession == nil); //it doesn't work, so I explicitly set the value
}

- (BOOL)isAsynchronous
{
    return YES;
}

- (void)cancel
{
    [super cancel];
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    self.isExecuting = NO;
    self.isFinished  = YES;
    [self didChangeValueForKey:@"isFinished"];
    [self didChangeValueForKey:@"isExecuting"];

    if(self.downloadTask.state == NSURLSessionTaskStateRunning)
        [self.downloadTask cancel];
    [self finish];
 } 
- (void)finish
{
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    self.defaultSession = nil; //NSURLSession
    self.isFinished  = YES;
    [self didChangeValueForKey:@"isFinished"];
    [self didChangeValueForKey:@"isExecuting"];
 }

Thank you in advance

EDIT: finally I found the issue - it was NSURLSession inside the queue. It kept strong reference to the queue and didn't allow it to be deallocated and removed from NSOperationQueue.


Solution

  • I have done the exact same thing, albiet in Swift.

    There are a couple of aspects that I have implemented differently and are listed below:

    • I have noticed that we do not have to override cancel() method in the Asynchronous operation. The default behavior of NSOperation's cancel method is to set the self.cancelled boolean to true.
    • self.executing should be set to true only inside the overriden start() method of operation, but not in the init ( Not sure if this wil cause any issues). Also before setting the self.executing = true in the start() method, I ensure that the boolean self.cancelled is false. If self.cancelled = true, we should set self.finished = true.
    • Also , I have created a "didSet" property observer for the isExecuting and isFinished properties where I call the willChangeValueForKey and didChangeValueForKey. I am not 100% sure how to replicate the didSet behavior in Obj C. ( This says to overrride setter)

    Please refer to the Ray Wenderlich video tutorials about "Concurrency" where Sam DAvies explains the creation of NSoperation subclass for Asynchronous operation. Please note, that it is only for subscribers and it is explained in Swift. I believe if you fix points 1 and 2, you should see your issues being fixed.