You could get a crash like you describe if you fail to make your operation a "concurrent" operation (i.e. one that returns true to isConcurrent
and posts isFinished
only after the asynchronous process is done). Without that, your MyOperation
object may be deallocated too soon because the operation is "finished" when the the initiation of the request is finished, rather than waiting for the response. For a discussion on concurrent operations, see the Configuring Operations for Concurrent Execution section of Operation Queues chapter of the Concurrency Programming Guide.
Furthermore, you might also want to make sure you maintain a strong reference to myObject
.
Thus:
@interface MyOperation ()
typedef void(^MyOperationCompletionBlock)(NSError *error, NSString *result);
@property (copy, nonatomic) MyOperationCompletionBlock myOperationCompletionBlock;
@property (strong, nonatomic) MyObject *myObject;
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@end
@implementation MyOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
- (id)initWithSymbol:(NSString*)symbol withCompletion:(MyOperationCompletionBlock)completionBlock
{
self = [super init];
if (self) {
_symbol = symbol;
_myOperationCompletionBlock = completionBlock;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
self.myObject = [[MyObject alloc]init];
[self.myObject downloadData:self.symbol withCompletion:^(NSDictionary *results, NSError *error) {
NSString *result = ... // presumably you're extracting this from `results` dictionary
if (self.myOperationCompletionBlock)
self.myOperationCompletionBlock(error, result);
[self completeOperation]; // this is the key; post the `isFinished` notification when done
}];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
@end
Also, when you make your session, if you're not writing delegate methods (which is implied by your use of the completion block rendition of dataTaskWithRequest
) you should use [NSURLSession sharedSession]
or [NSURLSession sessionWithConfiguration:configuration]
(but no delegate specified). Specifying a delegate
of nil
can cause problems. Thus:
- (void)downloadData:self:(NSString*)symbol withCompletion:(void(^)(NSDictionary* results, NSError *error))completionBlock
{
// ...
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[self.baseUrl stringByAppendingString:path]]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// **** THE APP CRASHES HERE RIGHT AFTER THE DATATASK STARTS. ****
// The completion block never gets called.
completionBlock(results, nil);
}];
[dataTask resume];
}
Unrelated, I'd also suggest:
Don't call your block property
completionBlock
.NSOperation
already has acompletionBlock
property (with a different signature). In my example above, I renamed this tomyOperationCompletionBlock
.Apple advises that you declare your block property with the
copy
memory semantic. If you use ARC, that's what it does, regardless, so they suggest declaring your property with the memory semantic that best reflects what's going on.