We have a reoccurring, difficult to reproduce crash in our app using ReactiveCocoa 2.5
We have a background scheduler which we retain during the live time of the application. Whenever we do a network call (using AFNetworking) we deliver the results (JSON data most of the time) on this background scheduler. We then store the data on a background core data context, to finally deliver a completion on the originating scheduler (in practice, the main scheduler).
An example of this flow in code:
- (RACSignal *)example {
NSString *path = _applicationConfiguration.competitionsPath;
@weakify(self)
RACSignal *networkCallSignal = [[RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
@strongify(self)
NSURLSessionDataTask *task = [self _GETDataTaskWithSubscriber:subscriber path:path parameters:nil];
return [RACDisposable disposableWithBlock:^{
[task cancel];
}];
}] deliverOn:_backgroundScheduler];
RACSignal *networkCallSignalToCoreDataSaveSignal = [[networkCallSignal flattenMap:^RACStream *(id result) {
@strongify(self)
NSError *error;
Team *team = [self _saveJSONToCoreData:result error:&error];
if (team) {
return [RACSignal return:team.identifier];
} else {
return [RACSignal error:error];
}
}] subscribeOn:_backgroundScheduler];
return [networkCallSignalToCoreDataSaveSignal deliverOn:[RACScheduler currentScheduler]];
}
- (Team *)_saveJSONToCoreData:(NSDictionary *)JSONData error:(NSError **)error {
//naive implementation, for the sake of this example
NSManagedObjectContext *context = ...;//context creation ommited for this example,
NSEntityDescription *ed = [NSEntityDescription entityForName:@"Team" inManagedObjectContext:context];
Team *team = [[Team alloc] initWithEntity:ed insertIntoManagedObjectContext:context];
//setting properties on team from json dictionary here
if ([context save:error]) {
return team;
} else {
return nil;
}
}
static id _afSessionManager;
- (NSURLSessionDataTask *)_GETDataTaskWithSubscriber:(id <RACSubscriber>)subscriber path:(NSString *)path parameters:(NSDictionary *)parameters {
return [_afSessionManager GET:path parameters:parameters success:[self _succesBlock:subscriber path:path] failure:[self _failBlock:subscriber path:path]];
}
- (void (^)(NSURLSessionDataTask *, NSError *))_failBlock:(id <RACSubscriber>)subscriber path:(NSString *)path {
return ^(NSURLSessionDataTask *t, NSError *error) {
[subscriber sendError:error];
};
}
- (void (^)(NSURLSessionDataTask *, id))_succesBlock:(id <RACSubscriber>)subscriber path:(NSString *)path {
void (^success)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask *t, id result) {
[subscriber sendNext:result];
[subscriber sendCompleted];
};
return success;
}
The crash happens in the [RACScheduler performAsCurrentScheduler:]
method, as in here (see CRASHES HERE comment):
- (void)performAsCurrentScheduler:(void (^)(void))block {
...
RACScheduler *previousScheduler = RACScheduler.currentScheduler;
NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self;
@autoreleasepool {
block(); //CRASHES HERE
}
...
}
The problem, as you can see in the crash log below, seems to be a release on a NSManagedObject. This occurs once in a while, hence hard to reproduce, but always with the same stack trace.
To be complete, here is a typical crash log:
Thread : Crashed: background.scheduler
0 libobjc.A.dylib 0x0000000196b17bd0 objc_msgSend + 16
1 CoreData 0x0000000184fa6a00 -[NSManagedObject release] + 160
2 CoreData 0x0000000184f9c490 -[_PFArray dealloc] + 100
3 libobjc.A.dylib 0x0000000196b1d724 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 564
4 exampleapp 0x00000001000c0ec8 -[RACScheduler performAsCurrentScheduler:] (RACScheduler.m:203)
5 libdispatch.dylib 0x0000000197155994 _dispatch_call_block_and_release + 24
6 libdispatch.dylib 0x0000000197155954 _dispatch_client_callout + 16
7 libdispatch.dylib 0x00000001971600a4 _dispatch_queue_drain + 1448
8 libdispatch.dylib 0x0000000197158a5c _dispatch_queue_invoke + 132
9 libdispatch.dylib 0x0000000197162318 _dispatch_root_queue_drain + 720
10 libdispatch.dylib 0x0000000197163c4c _dispatch_worker_thread3 + 108
11 libsystem_pthread.dylib 0x000000019733522c _pthread_wqthread + 816
12 libsystem_pthread.dylib 0x0000000197334ef0 start_wqthread + 4
Could it be that the autorelease pool surrounding the block, releases an already released NSManagedObject?
Any ideas?