NSBlockOperation EXC_BAD_ACCESS

时间:2015-05-09 13:09:01

标签: ios objective-c objective-c++

我创建了一个实现一些方法的类。这些方法由另一个类调用,并通过NSBlockOperation进行管理。

我的NSBlockOperation正常工作,当我尝试评估变量时遇到问题:

EXC_BAD_ACCESS

我在互联网上做了很多研究,this是最接近我问题的研究。我试着做sugerito,但你遇到同样的问题。

你有什么建议吗?

编辑:

这是堆栈跟踪:

2015-05-09 15:24:45.976 OutParameters[12326:743087] Stack trace : (
    0   OutParameters                       0x000000010e5d6602 -[ListOperation _method1:] + 194
    1   OutParameters                       0x000000010e5d646f __25-[ListOperation method1:]_block_invoke + 95
    2   Foundation                          0x000000010e74257f __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
    3   Foundation                          0x000000010e6830b2 -[NSBlockOperation main] + 98
    4   Foundation                          0x000000010e665774 -[__NSOperationInternal _start:] + 645
    5   Foundation                          0x000000010e665383 __NSOQSchedule_f + 184
    6   libdispatch.dylib                   0x00000001113f4614 _dispatch_client_callout + 8
    7   libdispatch.dylib                   0x00000001113db6a7 _dispatch_queue_drain + 2176
    8   libdispatch.dylib                   0x00000001113dacc0 _dispatch_queue_invoke + 235
    9   libdispatch.dylib                   0x00000001113de3b9 _dispatch_root_queue_drain + 1359
    10  libdispatch.dylib                   0x00000001113dfb17 _dispatch_worker_thread3 + 111
    11  libsystem_pthread.dylib             0x0000000111761637 _pthread_wqthread + 729
    12  libsystem_pthread.dylib             0x000000011175f40d start_wqthread + 13
)

修改后的代码:

- (IBAction)testCallMethod:(id)sender {
    NSString * output;
    [self.listOperationObj method1:&output];
    NSLog(@"Output: %@", output);
}

@interface ListOperation : NSObject

-(void)method1:(NSString**)output;

@end

#define MAX_OPERATIONS 10
//define a log-level
static int logLevel = CSLOG_LEVEL_INFO;
@interface ListOperation ()
// Tail used to synchronize the methods
@property NSOperationQueue *queue;
@end

#pragma mark - Public methods

@implementation ListOperation

- (id)init {
    self = [super init];
    if(self) {
        _queue = [NSOperationQueue new];
        if(_queue) {
            [_queue setMaxConcurrentOperationCount:1];
        }else {
            NSLog(@"TokenMgr creation failed: error creating operation queue");
            self = nil;
        }
    }
    return self;
}

-(void)method1:(NSString *__autoreleasing *)output{
    LOGFSTART
    if([self _isQueueFull] == FALSE) {
        WEAK
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            STRONG
            [strongSelf _method1:output];
        }];
        [self.queue addOperation:operation];
        [operation waitUntilFinished];
    }
    else {
        LOGE(@"TokenMgr's queue is full, aborting operation");
    }
    LOGFEND
}

#pragma mark - private methods

-(void)_method1:(NSString *__autoreleasing *)output{
    std::string testString = "try put string";
    *output = [NSString stringWithUTF8String:testString.c_str()];
}

- (BOOL) _isQueueFull {
    return self.queue.operationCount > MAX_OPERATIONS;
}

@end

如果我反复按下按钮,这次更改会给我带来同样的错误。

1 个答案:

答案 0 :(得分:1)

直接问题与块无关。您有一个代码段,上面写着:

- (IBAction)testCallMethod:(id)sender {
    NSString *__autoreleasing * output;
    [self.listOperationObj method1:output];
    NSLog(@"Output: %@", *output);
}

由于output不会指向有效的内存地址,并且当您尝试使用*output = ...取消引用未初始化的指针时,它不会起作用。

相反, 应该是:

- (IBAction)testCallMethod:(id)sender {
    NSString *output;
    [self.listOperationObj method1:&output];
    NSLog(@"Output: %@", output);
}

现在output引用了一个真实的NSString *指针,您可以使用对象的引用来填充它。

还有第二个更深层次的问题,即对在操作中实例化的对象使用* __autoreleasing *引用。操作有自己的自动释放池,所以你有一个竞争条件,当你在testCallMethod中使用它时释放对象。

相反,通常使用完成块将数据传递回调用者。因此:

- (IBAction)testCallMethod:(id)sender {
    [self.listOperationObj method2:^(NSString *output) {
        NSLog(@"Output: %@", output);
    }];
}

- (void)method2:(void (^)(NSString *))completionHandler {
    LOGFSTART
    if([self _isQueueFull] == FALSE) {
        WEAK
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            STRONG
            [strongSelf _method2:completionHandler];
        }];
        [self.queue addOperation:operation];
        // [operation waitUntilFinished];  // not needed any more
    }
    else {
        LOGE(@"TokenMgr's queue is full, aborting operation");
    }
    LOGFEND
}

-(void)_method2:(void (^)(NSString *))completionHandler {
    std::string testString = "try put string";
    NSString *output = [NSString stringWithUTF8String:testString.c_str()];
    completionHandler(output);
}

顺便说一下,这也解决了你的例子的另一个问题,你必须在那里放一个waitUntilFinished电话。你永远不应该从主线程中调用waitUntilFinished。如果您使用完成块,如上所述,则不再需要。