سؤال

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!!

هل كانت مفيدة؟

المحلول

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:

  1. 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.

  2. 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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top