我在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]
(但未指定委托)。指定delegate
的nil
可能会导致问题。从而:- (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];
}
无关的,我还建议:
completionBlock
。 NSOperation
已经具有completionBlock
属性(具有不同的签名)。在上面的示例中,我将其重命名为myOperationCompletionBlock
。 copy
内存语义声明您的块属性。如果使用ARC,无论如何,它就是这样做的,因此他们建议使用最能反映所发生情况的内存语义声明属性。