Reputation: 5634
I am using a NSURLSessionDataTask
in a NSOperationQueue
which leads to a crash of my application.
Unfortunately after changing many queue related parameters and reading through the documentation, I am still unable to find the reason of my bug.
I would appreciate any help and hints from you!
This is my AppDelegate.m (Cocoa) where I set up the queue and start the background operation. Please note that the operation has a completion handler:
@property (strong, nonatomic) NSOperationQueue *queue;
- (IBAction)startProcess:(id)sender {
self.queue = [NSOperationQueue new];
self.queue.maxConcurrentOperationCount = 1; // serial queue
MyOperation *myOperation = [[MyOperation alloc]initWithSymbol:@"abc"
withCompletion:^(NSError *error, NSString *result) {
NSLog(@"Process completed: %@",result);
}];
[self.queue myOperation];
}
This is MyOperation.h:
@interface MyOperation : NSOperation
MyOperation.m:
@interface MyOperation ()
typedef void(^completionBlock)(NSError *error, NSString *result);
@property (strong, nonatomic) completionBlock completionBlock;
@end
@implementation MyOperation
- (id)initWithSymbol:(NSString*)symbol withCompletion:
(void(^)(NSError *error, Order *order))completionBlock
{
self = [super init];
if (self) {
_symbol = symbol;
_completionBlock = completionBlock;
}
return self;
}
- (void)main {
MyObject *myObject = [[MyObject alloc]init];
[myObject downloadData:self.symbol withCompletion:
^(NSDictionary *results, NSError *error) {
//... }];
And this is MyObject.m where the application crashes in the method -downloadData
:
- (void)downloadData:self:(NSString*)symbol
withCompletion:(void(^)(NSDictionary* results, NSError *error))completionBlock
// ...
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:[self.baseUrl
stringByAppendingString:path]]];
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession
sessionWithConfiguration:sessionConfig delegate:self
delegateQueue:[NSOperationQueue currentQueue]];
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];
}
This is the crash log (Thread 2, 0__cxa_throw
):
libc++abi.dylib`__cxa_throw:
0x7fff8f6e1bdf: pushq %rbp
0x7fff8f6e1be0: movq %rsp, %rbp
0x7fff8f6e1be3: pushq %r15
0x7fff8f6e1be5: pushq %r14
0x7fff8f6e1be7: pushq %r13
0x7fff8f6e1be9: pushq %r12
0x7fff8f6e1beb: pushq %rbx
0x7fff8f6e1bec: pushq %rax
0x7fff8f6e1bed: movq %rdx, %r14
0x7fff8f6e1bf0: movq %rsi, %r15
0x7fff8f6e1bf3: movq %rdi, %rbx
0x7fff8f6e1bf6: callq 0x7fff8f6e17f4 ; __cxa_get_globals
0x7fff8f6e1bfb: movq %rax, %r12
0x7fff8f6e1bfe: callq 0x7fff8f6e2180 ; std::get_unexpected()
0x7fff8f6e1c03: movq %rax, -0x60(%rbx)
0x7fff8f6e1c07: callq 0x7fff8f6e21ba ; std::get_terminate()
0x7fff8f6e1c0c: leaq -0x20(%rbx), %r13
0x7fff8f6e1c10: leaq 0x44(%rip), %rcx ; __cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*)
0x7fff8f6e1c17: movabsq $0x434c4e47432b2b00, %rdx
0x7fff8f6e1c21: movq %rax, -0x58(%rbx)
0x7fff8f6e1c25: movq %r15, -0x70(%rbx)
0x7fff8f6e1c29: movq %r14, -0x68(%rbx)
0x7fff8f6e1c2d: movq %rdx, -0x20(%rbx)
0x7fff8f6e1c31: movq $0x1, -0x78(%rbx)
0x7fff8f6e1c39: incl 0x8(%r12)
0x7fff8f6e1c3e: movq %rcx, -0x18(%rbx)
0x7fff8f6e1c42: movq %r13, %rdi
0x7fff8f6e1c45: callq 0x7fff8f6e49cc ; symbol stub for: _Unwind_RaiseException
0x7fff8f6e1c4a: movq %r13, %rdi
0x7fff8f6e1c4d: callq 0x7fff8f6e1c7f ; __cxa_begin_catch
0x7fff8f6e1c52: movq -0x58(%rbx), %rdi
0x7fff8f6e1c56: callq 0x7fff8f6e21c9 ; std::__terminate(void (*)())
MyObject acts as an API to a web service and has methods to GET data from it.
MyOperation contains the business logic and controls the requests which are being sent to the API.
Imagine MyObject being the API to a stock broker and the methods being: getSharePrice, placeOrder and cancelOrder.
MyOperation defines the logic, e.g. sharePrice = getSharePrice(symbol:"AAPL"); while (sharePrice < 300) placeOrder("AAPL", 50) until allSharesBought = 1000.
Thank you for your help!!
Upvotes: 1
Views: 2514
Reputation: 437392
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 a completionBlock
property (with a different signature). In my example above, I renamed this to myOperationCompletionBlock
.
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.
Upvotes: 1