18

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!

1 Answer 1

38

OK - so after more hair-pulling and research, it finally dawned on me that it might be a 64-bit vs 32-bit issue. This was the first app I was working on since upgrading to the new Xcode. I went to Build Settings and changed the Architectures from "Standard architectures (including 64-bit) (armv7, armv7s, arm64)" to "Standard architectures (armv7, armv7s)". This fixed the issue!

I then went back and researched why this was happening. I found this Apple 64-bit transition guide: https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html

The document mentions the following:

Dispatch Objective-C Messages Using the Method Function’s Prototype An exception to the casting rule described above is when you are calling the objc_msgSend function or any other similar functions in the Objective-C runtime that send messages. Although the prototype for the message functions has a variadic form, the method function that is called by the Objective-C runtime does not share the same prototype. The Objective-C runtime directly dispatches to the function that implements the method, so the calling conventions are mismatched, as described previously. Therefore you must cast the objc_msgSend function to a prototype that matches the method function being called.

Listing 2-14 shows the proper form for dispatching a message to an object using the low-level message functions. In this example, the doSomething: method takes a single parameter and does not have a variadic form. It casts the objc_msgSend function using the prototype of the method function. Note that a method function always takes an id variable and a selector as its first two parameters. After the objc_msgSend function is cast to a function pointer, the call is dispatched through that same function pointer.

Using this information, I changed the below line:

objc_msgSend(self.handler, self.action, self.value);

to:

id (*response)(id, SEL, id) = (id (*)(id, SEL, id)) objc_msgSend; response(self.handler, self.action, output); 

And all is working!

Hopefully this will help someone else...

Sign up to request clarification or add additional context in comments.

8 Comments

Thank you for this. It's terrible that this issue doesn't show up on the 64-bit simulator!
could you see my question? I guess I'm doing something wrong: stackoverflow.com/questions/21843500/…
Thank you very much!! Been googling for a very long time over this :)
FWIW, I ran into this issue and resolved it by using performSelector: [self.handler performSelector:self.action withObject:self.value];
Use this code to support for both ARM 64 and 32,because above code will crash in 32 bit, ((void(*)(id, SEL, id))objc_msgSend)(self.handler, self.action, output);
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.