Edit - I've tracked the below issue to a 64-bit vs 32-bit architecture issue... see my posted answer for how I resolved
I've used SudzC to generate SOAP code for a web service. They supply you with a sample application, which I was able to use successfully, both on device and simulator.
I then started building out my app. I imported the SudzC generated files into a new XCode project using the blank application template (with CoreData and ARC enabled).
I got the first SOAP request up and running -- everything works in the simulator -- and then I went to do my first test on a device (iPhone 5S running iOS 7.02). The device throws an EXC_BAD_ACCESS
error every time the SOAP request is run.
I've tracked this down to the SoapRequest.m
file, specifically the connectionDidFinishLoading
method. This method uses a objc_msgSend
call to send the SOAP response data back to a handler method in another class (in this case, my view controller). Here's the code:
SoapRequest.m:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError* error;
if(self.logging == YES) {
NSString* response = [[NSString alloc] initWithData: self.receivedData encoding: NSUTF8StringEncoding];
NSLog(@"%@", response);
}
CXMLDocument* doc = [[CXMLDocument alloc] initWithData: self.receivedData options: 0 error: &error];
if(doc == nil) {
[self handleError:error];
return;
}
id output = nil;
SoapFault* fault = [SoapFault faultWithXMLDocument: doc];
if([fault hasFault]) {
if(self.action == nil) {
[self handleFault: fault];
} else {
if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
objc_msgSend(self.handler, self.action, fault);
} else {
NSLog(@"SOAP Fault: %@", fault);
}
}
} else {
CXMLNode* element = [[Soap getNode: [doc rootElement] withName: @"Body"] childAtIndex:0];
if(deserializeTo == nil) {
output = [Soap deserialize:element];
} else {
if([deserializeTo respondsToSelector: @selector(initWithNode:)]) {
element = [element childAtIndex:0];
output = [deserializeTo initWithNode: element];
} else {
NSString* value = [[[element childAtIndex:0] childAtIndex:0] stringValue];
output = [Soap convert: value toType: deserializeTo];
}
}
if(self.action == nil) { self.action = @selector(onload:); }
if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
objc_msgSend(self.handler, self.action, output);
} else if(self.defaultHandler != nil && [self.defaultHandler respondsToSelector:@selector(onload:)]) {
[self.defaultHandler onload:output];
}
}
conn = nil;
}
So the line objc_msgSend(self.handler, self.action, output);
seems to be where my issue is. self.handler
is pointing to my View Controller, and self.action
points to this method:
TasksViewController.m:
- (void) findItemHandler: (id) value {
// Handle errors
if([value isKindOfClass:[NSError class]]) {
NSLog(@"%@", value);
return;
}
// Handle faults
if([value isKindOfClass:[SoapFault class]]) {
NSLog(@"%@", value);
return;
}
// Do something with the id result
NSLog(@"FindItem returned the value: %@", value);
}
The re-entry to this method is where I crash out. It looks like the (id)value
is not making it over from the SoapRequest class. I assume it is getting deallocated by ARC. I've tested the call by replacing (id)value
with an int
:
objc_msgSend(self.handler, self.action, 1);
and
- (void)findItemHandler:(int)value
This works. Assuming the issue is the value variable getting prematurely destroyed, I tried a few things to try and keep it retained. I added a property to SoapRequest.m:
@property (nonatomic, strong) id value;
Then passed that:
self.value = output;
objc_msgSend(self.handler, self.action, self.value);
Same problem. I also tried the same thing with the instance of SoapRequest...Now, I was able to work around the issue by creating a property in the view controller and setting that property to the value, but I am really curious how to fix this using the original code. I went back to the example app I downloaded from SudzC to see how that was working, and it turns out ARC is not enabled for that project (!).
Can someone tell me:
1) If I am correct in my assumption that output
is being deallocated, causing the handler method to reference a bad memory address?
2) Why this works on the simulator? I assume it is because the sim has a lot more memory available so it isn't as aggressive with ARC deallocations...
3) How I could fix this, assuming I want to keep the objc_msgSend
call? I want to learn how/why this is happening
4) If SudzC is correct in their usage here of objc_msgSend
, as I understand it is bad practice to call this directly except in rare circumstances?
Thanks!
See Question&Answers more detail:
os