Got a really hard crash to solve. My iOS app (iOS version 6+, Xcode 5.1.1) crashed when the user logs out from his account, but only when it is forgrounded and backgrounded just before.
This is the crashlog from Testflight:
SIGSEGV
APP_NAME copy__destroy_helper_block_
in CAPServiceManager.m on Line 0
# Binary Image Name Address Symbol
0 APP_NAME copy 0x0010b61c testflight_backtrace
1 APP_NAME copy 0x0010ae5c TFSignalHandler
2 libsystem_platform.dylib 0x33d0087a _sigtramp
3 APP_NAME copy 0x000f180c __destroy_helper_block_ in CAPServiceManager.m on Line 0
4 libsystem_blocks.dylib 0x33bdbae0 _Block_release
5 Foundation 0x27268eb8
6 libobjc.A.dylib 0x33650d5e
7 Foundation 0x272ff372
8 libdispatch.dylib 0x33ba295e
9 libdispatch.dylib 0x33ba5ba6 _dispatch_main_queue_callback_4CF
10 CoreFoundation 0x2655fbd8
11 CoreFoundation 0x2655e2d8
12 CoreFoundation 0x264ac610 CFRunLoopRunSpecific
13 CoreFoundation 0x264ac422 CFRunLoopRunInMode
14 GraphicsServices 0x2da060a8 GSEventRunModal
15 UIKit 0x29bf6484 UIApplicationMain
16 APP_NAME copy 0x0009587a main in main.m on Line 16
17 libdyld.dylib 0x33bc0aae
In Xcode it is however crashing as a EXC_BAD_ACCESS in the AppDelegate file. (not giving more details). Enabling NSZombie does not work either as it prevents the app from crashing while this is on.
The code in CAPServiceManager related to blocks are these ones:
- (void)executeService:(WCServiceType)service
pin:(NSString *)pin
payLoad:(id)payload
usingBlock:(void (^) (NSError *error))block
{
[self addStartingServiceStatusForService:service];
[[WCWebService sharedWebService]
postAuthTokenForService:service
pin:pin
vehicle:_vehicle
target:self
usingBlock:^(NSError *error, id response) {
if (!error) {
[self executeService:service token:response payLoad:payload usingBlock:block];
}
else {
if (error.code == kWCHTTPStatusCodeUnauthorized) {
_wrongPinCounter++;
}
[self failStartingServiceStatusForServiceType:service error:error serviceAlreadyStarted:NO];
block(error);
}
}];
}
- (void)executeService:(WCServiceType)service
token:(NSString *)token
payLoad:(id)payload
usingBlock:(void (^) (NSError *error))block
{
[self addStartingServiceStatusForService:service];
[[WCWebService sharedWebService]
postStartServiceWithTarget:self
service:service
payLoad:payload
token:token
vehicle:_vehicle
usingBlock:^(id target, NSError *error, WCServiceStatus *serviceStatus) {
if (!error) {
WCServiceStatus *startingServiceStatus = [self serviceStatusForService:service];
NSManagedObjectContext *moc = _vehicle.managedObjectContext;
[moc performBlockAndWait:^{
startingServiceStatus.sentPayload = payload;
[startingServiceStatus updateWithServiceStatus:serviceStatus];
}];
block(nil);
}
else {
if (error.localizedDescription && [error.localizedDescription isEqualToString:@"Service is already started"]) {
[self serviceIsAlreadyStartedForServiceType:service block:^(NSError *error) {
if (error) {
[self failStartingServiceStatusForServiceType:service error:error serviceAlreadyStarted:YES];
}
block(error);
}];
}
else {
[self failStartingServiceStatusForServiceType:service error:error serviceAlreadyStarted:NO];
block(error);
}
}
}];
}
I first thought that the error was related to self. But testing to store self as this does not work either:
__weak CAPServiceManager *weakSelf = self;
did not work either. I also tried __block
as a modifier.
The blocks are passed on as you can see. The are then stored in an instance variable in WCWebServiceRequest.m like this:
_block = [block copy];
...where _block
is defined as
@interface WCWebServiceRequest : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate> {
@protected
id _block;
//...
...and later called upon with this code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSOperationQueue *queue = [NSOperationQueue new];
queue.name = [NSString stringWithFormat:@"%s Queue", __PRETTY_FUNCTION__];
[queue addOperationWithBlock:^{
NSError *error = nil;
NSDictionary *attributes;
__block WCServiceStatus *serviceStatus;
if (_data) {
attributes = [NSJSONSerialization JSONObjectWithData:_data options:0 error:&error];
}
if (self.response.statusCode == kWCHTTPStatusCodeAccepted || self.response.statusCode == kWCHTTPStatusCodeOK) {
if ([attributes isKindOfClass:[NSDictionary class]]) {
NSManagedObjectContext *moc = [WCStorage sharedStorage].moc;
[moc performBlockAndWait:^{
serviceStatus = [WCServiceStatus makeServiceStatusWithAttributes:attributes moc:moc];
}];
}
else {
error = [NSError errorWithWebServiceErrorCode:kWCHTTPStatusCodeInvalidData];
}
}
else {
error = [NSError errorWithWebServiceErrorCode:self.response.statusCode errorInfo:[NSError errorInfoFromData:_data]];
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (!_cancelled) {
WCWebServiceServiceStatusBlock_Invoke(_block, _target, error, serviceStatus);
}
[super connectionDidFinishLoading:connection];
}];
}];
}
... where WCWebServiceServiceStatusBlock_Invoke is defined as
#define WCWebServiceServiceStatusBlock_Invoke(block, target, error, serviceStatus) \
{ \
WCWebServiceServiceStatusBlock block_ = (WCWebServiceServiceStatusBlock) block; \
block_(target, error, serviceStatus); \
}
typedef void (^WCWebServiceServiceStatusBlock) (id target, NSError *error, WCServiceStatus *serviceStatus);
...and _block deallocated like this:
- (void)dealloc
{
if (_block) {
_block = nil;
}
}
Any idea what can be wrong or how to debug this further?
Edit: I am using ARC, and I can't answer why it is implemented this way, I took over an existing project.