Reputation: 6433
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
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:
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.
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