我在NSURLSessionDataTask中使用了NSOperationQueue,这导致我的应用程序崩溃。

不幸的是,在更改了许多与队列相关的参数并通读了文档之后,我仍然无法找到导致错误的原因。

谢谢您的帮助和提示!

这是我的 AppDelegate.m (可可),在其中设置队列并启动后台操作。请注意,该操作具有完成处理程序:

@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];
}

这是 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) {

      //... }];

这是 MyObject.m ,其中应用程序在-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];
}

这是崩溃日志(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 充当Web服务的API,并具有从中获取数据的方法。

MyOperation 包含业务逻辑并控制要发送到API的请求。

想象 MyObject 是股票经纪人的API,其方法是:getSharePrice,placeOrder和cancelOrder。

MyOperation 定义逻辑,例如sharePrice = getSharePrice(symbol:"AAPL"); while (sharePrice < 300) placeOrder("AAPL", 50) until allSharesBought = 1000.
谢谢您的帮助!

最佳答案

如果您无法使操作成为“并发”操作,则可能会发生崩溃,如您描述的那样(即,在异步过程完成后,返回isConcurrent为true并发布isFinished的操作)。否则,您的MyOperation对象可能会过早地被释放,因为在请求的发起完成时该操作“完成”了,而不是等待响应。有关并发操作的讨论,请参见《并发编程指南》中“操作队列”一章的Configuring Operations for Concurrent Execution部分。

此外,您可能还需要确保对myObject保持强烈引用。

从而:

@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

另外,在进行 session 时,如果您不编写委托方法(这是使用dataTaskWithRequest的完成块表示形式所隐含的),则应使用[NSURLSession sharedSession][NSURLSession sessionWithConfiguration:configuration](但未指定委托)。指定delegatenil可能会导致问题。从而:
- (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];
}

无关的,我还建议:
  • 不要调用您的块属性completionBlockNSOperation已经具有completionBlock属性(具有不同的签名)。在上面的示例中,我将其重命名为myOperationCompletionBlock
  • 苹果建议您使用copy内存语义声明您的块属性。如果使用ARC,无论如何,它就是这样做的,因此他们建议使用最能反映所发生情况的内存语义声明属性。
  • 08-07 04:37