user1028028
user1028028

Reputation: 6433

NSOperationQueue not releasing operation after it completes

I am creating a task (NSOperation) and putting it in an NSOperationQueue

   TaskJsonParser *parseTask = [[TaskJsonParser alloc] initWithJsonString:responseString andDelegate:self];
    [self.opQueue addOperation:parseTask];

When the task completes it calls a delegate method and sets it's flags to "finished". But Instruments shows it's still in memory... And indicates the above code as it's creator.

Why doesn't the queue release it after the operation completes. There is not other strong reference to it...

This is the code of the NSOperation

@interface TaskJsonParser ()

@property (strong, nonatomic) NSString *stringToParse;
@property (strong, nonatomic) SBJson4Parser *jsonParser;

@end

@implementation TaskJsonParser

#pragma mark - Custom initialization

- (instancetype)initWithJsonString:(NSString *)jsonString andDelegate:(id<JsonParsingDelegate>) delegate
{
    self = [super init];
    if (self) {
        _delegate = delegate;
        _stringToParse = jsonString;
        self.TAG = @"TaskJsonParse";

        // configure the parser
        SBJson4ValueBlock block = ^(id v, BOOL *stop) {
            BOOL isDictionary = [v isKindOfClass:[NSDictionary class]];
            if (isDictionary) {
                NSDictionary *parseResult = (NSDictionary *)v;
               // [self.delegate didFinishParsingJsonWithResult:parseResult];
                [(NSObject *)self.delegate performSelector:@selector(didFinishParsingJsonWithResult:) withObject:parseResult];

            } else {
                NSLog(@"json parsing did not result in a NSDictionary, returnig %@",NSStringFromClass([v class]));
                [(NSObject *)self.delegate performSelector:@selector(didFinishParsingJsonWithResult:) withObject:v];
              //  [self.delegate didFinishParsingJsonWithResult:v];
            }
        };

        SBJson4ErrorBlock errorBlock = ^(NSError* err) {
            NSLog(@"OOPS parsing error: %@", err);
            MvpError *e = [[MvpError alloc] initWithErrorObject:err andType:Parser];
      //      [self.delegate didFailToParseJsonWithError:e];
            [(NSObject *)self.delegate performSelector:@selector(didFinishParsingJsonWithResult:) withObject:e];
        };

        _jsonParser = [SBJson4Parser multiRootParserWithBlock:block
                                                 errorHandler:errorBlock];

          if (debugLog) { NSLog(@"Allocating Task: %@;",self.TAG); }
    }
    return self;
}

#pragma mark - Overriden NSObject methods 
// requierd for concurrent running

- (void)main
{
    @autoreleasepool {
        if (self.isCancelled) {
            [self completeOperation];
            return;
        }

        SBJson4ParserStatus responseCode = [self.jsonParser parse:[self.stringToParse dataUsingEncoding:NSUTF8StringEncoding]];

        if (responseCode == SBJson4ParserComplete) {
            NSLog(@"parse complete");
        } else if (responseCode ==  SBJson4ParserError) {
            NSLog(@"failed to parse");
        }
        [self completeOperation];
     }
}

Could thous block in the init... method cause the problem?

Upvotes: 1

Views: 1101

Answers (1)

Rob
Rob

Reputation: 438417

You have a strong reference cycle. Specifically, you have code that says:

SBJson4ValueBlock block = ^(id v, BOOL *stop) {
    BOOL isDictionary = [v isKindOfClass:[NSDictionary class]];
    if (isDictionary) {
        NSDictionary *parseResult = (NSDictionary *)v;
       // [self.delegate didFinishParsingJsonWithResult:parseResult];
        [(NSObject *)self.delegate performSelector:@selector(didFinishParsingJsonWithResult:) withObject:parseResult];

    } else {
        NSLog(@"json parsing did not result in a NSDictionary, returnig %@",NSStringFromClass([v class]));
        [(NSObject *)self.delegate performSelector:@selector(didFinishParsingJsonWithResult:) withObject:v];
      //  [self.delegate didFinishParsingJsonWithResult:v];
    }
};

So, the operation maintains a strong reference to the JSON parser, but the the parser uses a block which, itself, maintains a strong reference back to the operation.

You can remedy this by employing the weakSelf pattern:

typeof(self) __weak weakSelf = self;

SBJson4ValueBlock block = ^(id v, BOOL *stop) {
    BOOL isDictionary = [v isKindOfClass:[NSDictionary class]];
    if (isDictionary) {
        NSDictionary *parseResult = (NSDictionary *)v;
        [weakSelf.delegate didFinishParsingJsonWithResult:parseResult];
    } else {
        NSLog(@"json parsing did not result in a NSDictionary, returnig %@",NSStringFromClass([v class]));
        [weakSelf.delegate didFinishParsingJsonWithResult:v];
    }
};

Bottom line, refrain from using self inside the blocks of a strong property and you'll avoid your strong reference cycle. Repeat this edit in your error block, too.


Alternatively, I notice that you've made the JSON parser a class property. Because parse runs synchronously, that's unnecessary, and you could make the parser a local variable of main, and that would also resolve your strong reference cycle, without needing the above weakSelf pattern.


Below, please find general counsel that I originally provided prior to your code sample. The issue was the first bullet of point 2).


There are two common sources for this sort of problem:

  1. If this is a concurrent operation, remember to post isFinished KVN, e.g., when changing your _finished ivar, do something like:

    [self willChangeValueForKey:@"isFinished"];
    _finished = YES;
    [self didChangeValueForKey:@"isFinished"];
    

    And, of course, you'd have an isFinished method, too:

    - (BOOL)isFinished
    {
        return _finished;
    }
    

    Repeat this process with executing, too.

    Personally, I wrap this logic in executing and finished properties, which I can share if you need it, but I wanted to focus this answer on the basic requirements (that you have a isFinished method and you post the isFinished KVN).

    For more details, see the Configuring Operations for Concurrent Execution section of the Operation Queues chapter of the Concurrency Programming Guide.

  2. Make sure you don't have a strong reference cycle (a.k.a. a retain cycle) within the operation itself. Common strong reference cycles include:

    • class properties that are blocks that either explicitly reference self or implicitly do so by referencing some ivar;

    • repeating timers for which you neglect to call invalidate before terminating the operation; or

    • any self referencing properties (e.g. delegates) that are accidentally defined as strong.

    In Instruments, you can use the "Record reference counts" feature (described in the latter part of this answer) to identify what, if anything, is maintaining a strong reference to your operation.

Upvotes: 1

Related Questions