Copy_destroy_helper_block_ crash on line 0
It was a very hard fall. My iOS app (iOS version 6+, Xcode 5.1.1) crashed when the user logged out of their account, but only when it is pre-configured and pre-installed.
This is the crash log 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, however, it crashes as EXC_BAD_ACCESS in the AppDelegate. (without giving more details). Enabling NSZombie does not work as it prevents the application from crashing while it is being enabled.
The code in CAPServiceManager related to blocks is as follows:
- (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);
}
}
}];
}
At first I thought the error was self-related. But testing to store itself, since that doesn't work either:
__weak CAPServiceManager *weakSelf = self;
doesn't work either. I have also tried __block
as a modifier.
The blocks are being transferred as you can see. They 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 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 are freed like this:
- (void)dealloc
{
if (_block) {
_block = nil;
}
}
Any idea what might be wrong or how to debug this further?
Edit: I am using ARC and I cannot answer why it is implemented this way, I took over an existing project.
source to share
This problem has been associated with many problems. Here are the steps I took to finally get rid of the problem:
- Make sure all NSNotificationsCenter registrations removed in dealloc
- Make sure KVO listeners are "ignored" (via BOOL + check) before they are removed (so as not to run code that interfered with the logout)
- Moved registration code (including clearing CoreData and unregistering notifications and network request) to split queues:
Related new code:
- (void)userDidSignOut
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[WCWebService sharedWebService] signOut];
[_weatherRefresher stop];
[_TARefreshTimer invalidate];
[CAPSVTMessageView hide];
[[WCStorage sharedStorage].validV enumerateObjectsUsingBlock:^(WCV *obj, NSUInteger idx, BOOL *stop) {
[self unregisterFromPushNotificationsForVehicle:obj];
[obj.VHSRefresher kill];
[obj.serviceManager kill];
}];
NSOperationQueue *queue = [NSOperationQueue new];
queue.name = [NSString stringWithFormat:@"%s Queue", __PRETTY_FUNCTION__];
[queue addOperationWithBlock:^{
[[WCStorage sharedStorage] clearDatabase];
[WCStorage sharedStorage].sessionPassword = nil;
_signInFromBackround = NO;
}];
}];
}
source to share